企業專案遷移go-zero全攻略(二)

Kevin Wan發表於2021-01-25

承接上篇:上篇文章講到 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 = 100001
const 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 支援我們 ?


我為大家整理了 go-zero 作者去年廣受好評的分享視訊,詳細分享了 go-zero 的設計理念和最佳實踐。關注公眾號「微服務實踐」,回覆 視訊 獲取;還可以回覆 進群 和數千 go-zero 使用者交流學習。

go-zero 系列文章見『微服務實踐』公眾號

相關文章