使用go-zero快速生成接口的时候,发现还是有一些情况不太好处理,比如说,想要自定义响应封装等等。
最开始第一版写api文件的时候,写法是这样的。
type LoginRequest {
UserName string `json:"userName"`
Password string `json:"password"`
}
type Response {
Code int `json:"code"`
Data string `json:"data"`
Msg string `json:"msg"`
}
type UserInfo {
UserName string `json:"userName"`
Addr string `json:"addr"`
Id uint `json:"id"`
}
type UserInfoResponse {
Code int `json:"code"`
Data UserInfo `json:"data"`
Msg string `json:"msg"`
}
service users {
@handler login
post /api/users/login (LoginRequest) returns (Response)
@handler userInfo
get /api/users/info returns (UserInfoResponse)
}
// goctl api go -api v1.api -dir .
后面发现可以不把code、data、msg这三个重要信息写在api里边,而是通过统一封装,在统一响应中去加上code、data、msg,使之成为我们一个公共的库供我们使用。
首先我们封装好response文件:
package response
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
)
type Body struct {
Code uint32 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// Response http返回
func Response(r *http.Request, w http.ResponseWriter, resp interface{}, err error) {
if err == nil {
//成功返回
r := &Body{
Code: 0,
Msg: "成功",
Data: resp,
}
httpx.WriteJson(w, http.StatusOK, r)
return
}
//错误返回
errCode := uint32(7)
// 可以根据错误码,返回具体错误信息
//errMsg := "服务器错误"
httpx.WriteJson(w, http.StatusOK, &Body{
Code: errCode,
Msg: err.Error(),
Data: nil,
})
}
此时还需要考虑问题,假设我们只是需要这个项目使用统一的封装,不希望后面的go-zero项目受到影响,那么我们可以通过直接在本地生成模版文件去给当前这个项目使用。
然后我看了go-zero的官方文档,发现确实可以这么操作,ok,尝试一波。
首先,通过在本地(项目路径下)生成模版文件:
goctl template init --home template
然后可以看到提示成功了,并且生成了下面的模版。
下面是原api中的hanlder.tpl文件的代码:
package {{.PkgName}}
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
{{.ImportPackages}}
)
{{if .HasDoc}}{{.Doc}}{{end}}
func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
{{if .HasRequest}}var req types.{{.RequestType}}
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
{{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}}
}
}
}
修改后的tpl文件如下:
package handler
import (
"net/http"
"fim/common/response"
{{.ImportPackages}}
{{if .HasRequest}}
"github.com/zeromicro/go-zero/rest/httpx"
{{end}}
)
func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
{{if .HasRequest}}var req types.{{.RequestType}}
if err := httpx.Parse(r, &req); err != nil {
response.Response(r, w, nil, err)
return
}{{end}}
l := logic.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
{{if .HasResp}}response.Response(r, w, resp, err){{else}}response.Response(w, nil, err){{end}}
}
}
简单来分析下这个代码逻辑,就是使用了模板引擎的语法(如 {{.HandlerName}}
和 {{if .HasRequest}})
,这些模板变量和条件判断在代码生成时会被具体的值替换。
首先,“fim/common/response
”:项目中自定义的模块,提供了统一的响应处理方法。
然后是:{{.ImportPackages}}
:模板变量,表示在代码生成时会根据需要动态导入其他必要的包。
{{.ImportPackages}}
{{if .HasRequest}}
"github.com/zeromicro/go-zero/rest/httpx"
{{end}}
func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc
:{{.HandlerName}}
是模板变量,表示具体的处理函数名称。例如,它可能会被替换为 LoginHandler
。
svcCtx *svc.ServiceContext
:服务上下文,包含了依赖注入的配置和数据库连接等信息。然后返回handlerFunc
。
如果当前接口需要解析请求体({{if .HasRequest}}
),则会执行以下操作:定义一个变量 req,类型为 types.{{.RequestType}}
。{{.RequestType}}
是模板变量,表示请求体的类型,例如 LoginRequest
。
{{if .HasRequest}}var req types.{{.RequestType}}
if err := httpx.Parse(r, &req); err != nil {
response.Response(r, w, nil, err)
return
}{{end}}
使用 httpx.Parse(r, &req)
解析 HTTP 请求体到 req 中。如果解析失败(err != nil),调用 response.Response
方法返回错误响应,并结束处理。
然后是进行逻辑处理:创建一个业务逻辑对象 l
,类型为 logic.New{{.LogicType}}
。{{.LogicType}}
是模板变量,表示具体的业务逻辑类型,例如 LoginLogic。
调用业务逻辑对象的 {{.Call}}
方法({{.Call}} 是模板变量,表示具体的业务逻辑方法名,例如 Login
)。
l := logic.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
{{if .HasResp}}response.Response(r, w, resp, err){{else}}response.Response(w, nil, err){{end}}
如果接口有返回值({{if .HasResp}}
),则将返回值存储到 resp
中;否则,仅处理错误。
如果接口有返回值({{if .HasResp}}
),调用 response.Response(r, w, resp, err)
,将业务逻辑的返回值作为响应数据返回。
如果接口没有返回值,仅调用 response.Response(w, nil, err)
,返回错误信息或成功状态。
然后把原来的api生成的handler和logic删除,然后重新生成一下。注意选对好对应目录下的template(这里我是在auth_api路径下去运行这个命令,也就是跟api文件同级目录下去运行)。
goctl api go -api auth_api.api -dir . --home ../../template
运行成功,看看效果。
这个时候发现生成的接口函数那些就没问题了。