自定义Service


本章将基于“快速创建HAS API服务”引导你创建一个自己的API服务,且可以通过http访问到

创建Service项目结构

在项目目录下执行命令,创建用于存放工程的文件夹

mkdir -p models/services

创建一个目录用于存放service代码,并在其目录下创建service.goconf.goslot.go

cd models/services
mkdir testsvs
cd testsvs
touch service.go conf.go slot.go

完成以上操作后,你的项目结构应该看起来像是这样:

.
├── api.json
├── conf.toml
├── go.mod
├── go.sum
├── main.go
└── models
    └── services
        └── testsvs
            ├── conf.go
            ├── service.go
            └── slot.go

我们建议Service按照xxxsvs的命名规则来命名

编写Service代码

在对应的文件中写入代码

  1. service.go
    service.go文件主要用于处理和service相关的操作,例如在此service被注册时操作数据库初始化相关数据或service被注册时发起一个http请求等...
package testsvs

import (
	"github.com/drharryhe/has/common/herrors"
	"github.com/drharryhe/has/common/htypes"
	"github.com/drharryhe/has/core"
)

type Service struct {
	core.Service
	conf     TestService
	TestName string // testName,你可以自定义这些字段,用于存储必要信息
}
// 我们建议在项目开发中,开发者为此处自定义的字段都写上注释,便于后期维护

func (this *Service) Open(s core.IServer, instance core.IService, args htypes.Any) *herrors.Error {
	if err := this.Service.Open(s, instance, args); err != nil {
		return err
	}
  this.TestName = ""
	// 使用配置文件中的值
	if this.conf.TestBool {
		this.TestName = this.conf.TestName
	}
	return nil
}

func (this *Service) EntityStub() *core.EntityStub {
	return core.NewEntityStub(&core.EntityStubOptions{Owner: this})
}

func (this *Service) Config() core.IEntityConf {
	return &this.conf
}


  1. conf.go
    conf.go主要用于描述配置文件中服务对应的结构体,它需要继承core.ServiceConf
package testsvs

import "github.com/drharryhe/has/core"

type TestService struct {
	core.ServiceConf // 注意此处继承core.ServiceConf
}
  1. slot.go
    slot.go用于处理接口的业务逻辑
package testsvs

import (
	"github.com/drharryhe/has/common/herrors"
	"github.com/drharryhe/has/common/hlogger"
	"github.com/drharryhe/has/core"
)

type TestRequest struct {
	core.SlotRequestBase // 注意继承 core.SlotRequestBase
	// 注意类型要带指针,才能使用data验证
	Name   *string `json:"name" param:"require"`   // 名称
	Change *bool   `json:"change" param:"require"` // 是否改变name param:"require"代表此字段必传
}
// 我们建议在项目开发中,开发者为此处的每个字段都写上注释,便于后期维护

func (this *Service) TestSlot(req *TestRequest, res *core.SlotResponse) {
	hlogger.Info("request name:", *req.Name) // HAS中带有log组件可以直接使用
	hlogger.Info("request change:", *req.Change)
	resultName := *req.Name
	if *req.Change {
		resultName = this.TestName
	}
	if resultName == "" {
		// 定义error debug模式下 New里面的内容会直接以log输出 所以不需要单独打log
		res.Error = herrors.ErrSysInternal.New("名称为空").D("描述错误")
		return
	}
	// 返回数据
	this.Response(res, map[string]interface{}{
		"change":     *req.Change,
		"resultName": resultName,
	}, nil)
}

修改main.go、conf.toml和api.json

  1. main.go
func main() {
	// 创建gateway
	gateway := core.NewAPIGateway(&core.APIGatewayOptions{
		// Server配置
		ServerOptions: core.ServerOptions{
			// 路由配置
			Router: hlocalrouter.New(),
		},
		Connectors: []core.IAPIConnector{
			hwebconnector.New(), // Web服务
		},
		Packers: []core.IAPIDataPacker{
			hjsonpacker.New(),
		},
	})
	// 注册服务
	gateway.Server().RegisterService(&hellosvs.Service{}, nil)
+++	gateway.Server().RegisterService(&testsvs.Service{}, nil) // 添加testsvs
	// 启动服务
	gateway.Start()
}
  1. conf.toml 添加以下内容
[TestService]
Name = 'test' // Name是必须的
TestBool = true
TestName = 'HAS'
  1. api.json
{
  "name": "HAS api",
  "versions": [
    {
      "version": "v1",
      "apis": [
        {
          "name": "hello",
          "desc": "hello",
          "disabled": false,
          "endpoint": {
            "service": "hello",
            "slot": "HelloSlot"
          }
        },
// ==== 新增 ====
+++        {
+++          "name": "test",  // 请求接口名, http://127.0.0.1:8989/v1/test
+++          "desc": "test", // 接口描述
+++          "disabled": false, // 屏蔽接口
+++          "endpoint": {
+++            "service": "test", // 对应配置文件中的Service的name
+++            "slot": "TestSlot" // 对应slot.go中的方法名
+++          }
+++        }
// === 新增 ===
      ]
    }
  ]
}

运行

go run main.go

以下访问使用POST方法 body, Content-Type: application/json
如果使用GET方法访问,bool值会被解析为string类型,如果开发过程中遇到,需要自行转换

访问http://127.0.0.1:8989/v1/testopen in new window,参数如下:

{
    "name": "张三",
    "change":false
}

我们会得到结果

{
    "data": {
        "resultName": "张三",
        "change": false
    },
    "error": {
        "code": 0,
        "desc": "",
        "cause": ""
    }
}

如果参数如下

{
    "name": "张三",
    "change": true
}

会得到返回结果如下

{
    "data": {
        "change": true,
        "resultName": "HAS"
    },
    "error": {
        "code": 0,
        "desc": "",
        "cause": ""
    }
}

可以看到resultName为配置文件中的TestName了

如果参数如下:

{
    "name": "",
    "change": false
}

会得到返回结果如下,并在log中显示error调用信息

{
    "data": {},
    "error": {
        "code": 101,
        "desc": "此处信息会返回前端",
        "fingerprint": "56d1aa7de1e8f373a137e2623180271e",
        "cause": "名称为空"
    }
}

提示

HAS有一套很好的错误处理方案
在debug模式下会打一个error的log和详细的调用信息,便于我们在开发中快速定位问题
此处的fingerprint只有在Debug模式下才会返回,主要用于在一些无法进入服务器查看log的情况下定位问题

服务之间的调用

当我们在某个service内需要调用另一个service的接口时,我们可以使用下面这种方法来调用
testsvsOpen方法中插入以下代码,然后重启,并查看log

如上述代码所示,通过this.Server().RequestService('serviceName', 'slotName', params)便可以访问到对应的slot