使用go-zero框架的时候,发现在API请求过程中如果出现错误,接口会直接返回http400错误。这对前端或者其它服务端很不友好,他们需要获得详细错误信息,并且不返回http错误。同时,对于有错误的请求和成功的请求,接口返回的数据不一致。
总的来说,我们需要解决以下三个问题:
处理框架自带参数解析失败后产生的error(需自己处理)
处理逻辑层处理失败后产生的error(官方已处理)
修改模板,统一接口返回值(官方已处理)
后两个官方已处理,并加入官方学习文档,可自行查阅:
逻辑层错误官方解决方案:官方错误处理方法
统一接口返回值官方解决方案:官方统一请求返回方法
目前主要需要解决的就是框架自带的参数解析方法产生的错误,导致的http状态400问题,具体根据对应业务选择是否修改。我们以/num/add接口为例,一起学习一下整个修改过程,欢迎评论区交流~
根据官方文档自动生成接口项目代码
新建单个服务参考官方文档,单体服务创建
根据官方文档,一步一步操作就可以了。已经实践过的也可以直接下载源码进行修改,点击下载greet源码
新增一个post接口,实现两个整数相加的功能,核心代码如下:
修改greet.api文件,末尾添加如下代码:
type NumRequest {
FirstNum int `json:"first"`
SecondNum int `json:"second"`
}
type NumResponse {
Num int `json:"num"`
}
service greet-api {
@handler NumHandler
post /num/add(NumRequest) returns (NumResponse)
}
切换到greet根目录,使用goctl自动生成代码:
$ goctl api go -api greet.api -dir .
logic/numlogic.go 文件的Num方法内容修改如下:
func (l *NumLogic) Num(req types.NumRequest) (resp *types.NumResponse, err error) {
total := req.FirstNum + req.SecondNum
return &types.NumResponse{
Num: total,
}, nil
}
启动服务,命令如下:
$ go run greet.go
出现如下界面,服务启动成功:
Starting server at 0.0.0.0:8888...
接口调试
接口URL http://127.0.0.1:8888/num/add
正常情况请求正常情况返回
{
"first": 12,
"second": 223
}
异常情况请求异常情况返回400
{
"first2": 12,
"second": 223
}
代码修改
greet文件夹下新建目录/common/errorx,新建文件baseerror.go,用来处理error。
$ mkdir common&&cd common
$ mkdir errorx&&cd errorx
$ vim baseerror.go
通过实现error类型的Error方法,继承error类型,同时对CodeError并增加Data方法和NewSuccessJson统一返回结果
此处为了方便所有处理都放在这里,实际使用可根据情况分离成功和错误处理
此处为了方便所有处理都放在这里,实际使用可根据情况分离成功和错误处理
package errorx
const successCode = 0
const successMsg = "接口请求成功"
const defaultCode = 1001
const paramsFailedCode = 1002
type CodeError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// CodeErrorResponse 此处结构体用于统一返回结果
type CodeErrorResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func NewCodeError(code int, msg string) error {
return &CodeError{Code: code, Msg: msg}
}
// NewDefaultError 设置默认错误码
func NewDefaultError(msg string) error {
return NewCodeError(defaultCode, msg)
}
// NewParamsFailedError 设置接口参数json校验错误码
func NewParamsFailedError(msg string) error {
return NewCodeError(paramsFailedCode, msg)
}
func (e *CodeError) Error() string {
return e.Msg
}
// Data /**返回通用错误数据结构
func (e *CodeError) Data() *CodeErrorResponse {
return &CodeErrorResponse{
Code: e.Code,
Msg: e.Msg,
}
}
//NewSuccessJson /**接口请求成功返回数据
func NewSuccessJson(resp interface{}) *CodeErrorResponse {
return &CodeErrorResponse{
Code: successCode,
Msg: successMsg,
Data: resp,
}
}
修改handler层numhandler.go的NumHandler方法,处理json解析、逻辑层错误和正确的返回结果。注释部分为之前代码
func NumHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.NumRequest
if err := httpx.Parse(r, &req); err != nil {
//httpx.Error(w, err)
httpx.Error(w, errorx.NewParamsFailedError(err.Error()))
return
}
l := logic.NewNumLogic(r.Context(), svcCtx)
resp, err := l.Num(req)
if err != nil {
httpx.Error(w, err)
} else {
//httpx.OkJson(w, resp)
httpx.OkJson(w, errorx.NewSuccessJson(resp))
}
}
}
修改逻辑层numlogic.go文件Num方法返回的error,添加代码,当第一个数小于0返回错误:
func (l *NumLogic) Num(req types.NumRequest) (resp *types.NumResponse, err error) {
//此处为新增代码
if req.FirstNum < 0{
return nil, errorx.NewCodeError(1003, "第一个参数不允许小于0")
}
//此处新增结束
total := req.FirstNum + req.SecondNum
return &types.NumResponse{
Num: total,
}, nil
}
通过判断当前返回的error是自定义CodeError还是系统error,在greet.go中添加代码:
handler.RegisterHandlers(server, ctx)
// 此处为添加代码,当错误类型为自定义错误时,返回统一格式
httpx.SetErrorHandler(func(err error) (int, interface{}) {
switch e := err.(type) {
case *errorx.CodeError:
return http.StatusOK, e.Data()
default:
return http.StatusInternalServerError, nil
}
})
//此处添加结束
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
再次测试
正常情况请求
请求参数返回结果
{
"first": 12,
"second": 223
}
参数key错误情况请求(其它json解析错误可自己尝试,例如参数值类型错误)
请求参数返回结果
{
"first2": 12,
"second": 223
}
逻辑错误情况请求(其它可自己尝试,当前设置为第一个参数小于0)
请求参数返回结果
{
"first": -12,
"second": 223
}
可以看到,错误请求也都会返回相同的数据结构,代码添加成功。
修改模板
如果使用上述方法修改,对于自动生成的handler文件每次都要修改,这样就太麻烦,我们需要直接修改生成的模板。
按照官方文档一步步操作即可,点击查看官方自定义模板
此为我的修改过程
初始化模板(已初始化的跳过此步骤)
$ goctl template init
找到handler模板文件开始修改
$ vim ~/.goctl/1.3.2/api/handler.tpl
修改代码关键部分
修改前代码修改后代码
package {{.PkgName}}
import (
"net/http"
{{if .After1_1_10}}"github.com/zeromicro/go-zero/rest/httpx"{{end}}
{{.ImportPackages}}
)
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.Error(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.Error(w, err)
} else {
{{if .HasResp}}httpx.OkJson(w, resp){{else}}httpx.Ok(w){{end}}
}
}
}
修改完成之后,在greet.api文件新增一个接口,使用goctl自动生成代码,测试通过
总结
因为接口返回参数的问题,我们详细了解了go-zero框架中参数json解析错误、逻辑层错误处理、接口返回值统一处理问题。
在实际使用过程中,还需要根据具体情况分析处理,切忌死板硬套。
如果有更好的方法或想法也请大家多多指教~