Go-zero框架修改模版进行handler统一响应封装
使用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 loginpost /api/users/login (LoginRequest) returns (Response)@handler userInfoget /api/users/info returns (UserInfoResponse)
}// goctl api go -api v1.api -dir .
后面发现可以不把code、data、msg这三个重要信息写在api里边,而是通过统一封装,在统一响应中去加上code、data、msg,使之成为我们一个公共的库供我们使用。
首先我们封装好response文件:
package responseimport ("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 handlerimport ("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
运行成功,看看效果。
这个时候发现生成的接口函数那些就没问题了。