写点什么

企业项目迁移 go-zero 全攻略(二)

用户头像
Kevin Wan
关注
发布于: 2021 年 01 月 29 日

作者:Mikael


承接上篇:上篇文章讲到 go-zero 架构设计和项目设计。本篇文章接着这个项目设计,将生成的 app 模块gatewayRPC 进行改造。废话不多说,让我们开始!


gateway service


gateway 中我做了一些自定义,在端请求我们后台接口情况下,虽然多数情况是不需要关心错误码的,但是避免不了要某些场景还是需要根据固定错误码去做特殊处理,我自己定义了一个错误类,这个错误类只在 gateway 中使用:


err.go:


package xerr
import "fmt"
type CodeError struct { errCode int errMsg string}
// 属性func (e *CodeError) GetErrCode() int { return e.errCode}
func (e *CodeError) GetErrMsg() string { return e.errMsg}
func (e *CodeError) Error() string { return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg)}
func New(errCode int, errMsg string) *CodeError { return &CodeError{errCode: errCode, errMsg: errMsg}}
func NewErrCode(errCode int) *CodeError { return &CodeError{errCode: errCode, errMsg: MapErrMsg(errCode)}}
func NewErrMsg(errMsg string) *CodeError { return &CodeError{errCode: BAD_REUQEST_ERROR, errMsg: errMsg}}
复制代码


errmsg.go


package xerr
var message map[int]string
func init() { message = make(map[int]string) message[OK] = "SUCCESS" message[BAD_REUQEST_ERROR] = "服务器繁忙,请稍后再试" message[REUQES_PARAM_ERROR] = "参数错误" message[USER_NOT_FOUND] = "用户不存在"}
func MapErrMsg(errcode int) string { if msg, ok := message[errcode]; ok { return msg } else { return "服务器繁忙,请稍后再试" }}
复制代码


errcode.go


package xerr
// 成功返回const OK = 200
// 全局错误码// 前3位代表业务,后三位代表具体功能const BAD_REUQEST_ERROR = 100001const REUQES_PARAM_ERROR = 100002
// 用户模块const USER_NOT_FOUND = 200001
复制代码


我将三个文件统一放在 lib/xerr 目录



有了错误码还不行,还要定义统一返回 http 的结果,goctl 生成的默认的是挺好的,但是没法符合我这种返回自定义错误码需求,于是我自己有写了一个统一返回结果的文件:


httpresult:


package xhttp
import ( "fishtwo/lib/xerr" "fmt" "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/rest/httpx" "google.golang.org/grpc/status" "net/http" "github.com/pkg/errors")
// http方法func HttpResult(r *http.Request,w http.ResponseWriter,resp interface{},err error) { if err == nil { // 成功返回 r:= Success(resp) httpx.WriteJson(w, http.StatusOK, r) } else { // 错误返回 errcode := xerr.BAD_REUQEST_ERROR errmsg := "服务器繁忙,请稍后再试" if e,ok := err.(*xerr.CodeError);ok{ // 自定义CodeError errcode = e.GetErrCode() errmsg = e.GetErrMsg() } else { originErr := errors.Cause(err) // err类型 if gstatus, ok := status.FromError(originErr);ok{ // grpc err错误 errmsg = gstatus.Message() } } logx.WithContext(r.Context()).Error("【GATEWAY-SRV-ERR】 : %+v ",err)
httpx.WriteJson(w, http.StatusBadRequest, Error(errcode,errmsg)) }}
// http参数错误返回func ParamErrorResult(r *http.Request,w http.ResponseWriter,err error) { errMsg := fmt.Sprintf("%s ,%s", xerr.MapErrMsg(xerr.REUQES_PARAM_ERROR), err.Error()) httpx.WriteJson(w, http.StatusBadRequest, Error(xerr.REUQES_PARAM_ERROR,errMsg))}
复制代码


responsebean


package xhttp
type ( NullJson struct {}
ResponseSuccessBean struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` })
func Success(data interface{}) *ResponseSuccessBean { return &ResponseSuccessBean{200, "OK", data}}

type ResponseErrorBean struct { Code int `json:"code"` Msg string `json:"msg"`}
func Error(errCode int,errMsg string) *ResponseErrorBean { return &ResponseErrorBean{errCode, errMsg}}
复制代码


放在 lib/xhttp 下



然后改造了 internal/handler/下通过 goctl 生成的代码:



当然你会说,每次生成完都要手动去改,好麻烦!


当当当当~~~ goctltemplate 来咯 https://www.yuque.com/tal-tech/go-zero/mkpuit


然后修改 ~/.goctl/api/handler.tpl:


package handler
import ( "net/http"
{{.ImportPackages}})
func {{.HandlerName}}(ctx *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 { xhttp.ParamErrorResult(r,w,err) return }{{end}}
l := logic.New{{.LogicType}}(r.Context(), ctx) resp, err := l.Login(req) xhttp.HttpResult(r,w,resp,err) }}
复制代码


再重新生成看看,是不是就 beautiful 了,哈哈


然后在说我们的 gateway log,如果眼神好的用户,在上面的 httpresult.go 中已经看到了 log 的身影:



是的是的,这样处理就可以啦,这样只要有错误就会打印日志了,go-zero 已经把 trace-id 带进去了,啥?trace-id 不知道是啥?嗯,其实就是把一次请求通过此 id 串联起来,比如你 user-api 调用 user->srv 或者其他 srv,那要把他们这一次请求都串联起来,需要一个唯一标识别,这个 id 就是做这个,做链路追踪有很多,比如 jaegerzipkin


RPC service



modelrpc 服务中,官方文档推荐是将 model 放在 services 目录下,与每个 rpc 服务一层,但是个人感觉每个 model 对应一张表,一张表只能由一个服务去控制,哪个服务控制这张表就哪个服务拥有控制这个 model 权利,其他服务想访问就要通过 grpc,这是个人的想法,所以我把每个服务自己管控的 model 放在了 internal


enum:另外我在服务下加了 enum 枚举目录,因为其他 rpc 服务或者 api 服务会调用这个枚举去比对,我就放在 internal 外部



框架地址


https://github.com/tal-tech/go-zero


欢迎使用 go-zerostar 支持我们 👍


发布于: 2021 年 01 月 29 日阅读数: 755
用户头像

Kevin Wan

关注

保持简单 2017.10.24 加入

go-zero作者

评论

发布
暂无评论
企业项目迁移go-zero全攻略(二)