问题
在使用 Go gin 搭建 api 服务,当服务出现异常,或是业务自定义异常,如何区分异常类型?
栗子:
- 比如:user api ,当添加 user 失败时,返回
{
code: 10001,
message: "添加用户失败"
}
- 比如: order api ,当获取订单详情异常
{
code: 20001,
message: "获取订单详情异常"
}
可以结合 Go 语言特性,结合 painc、recover 来处理异常,返回统一数据格式
结合使用 gin 中间件来处理
Gin 框架自带中间件
在启动 Gin 服务,有 log 输出
- gin 启动已经初始化 Logger 、Recovery 中间件
- 查看源码
从源码中发现
-
gin 加载中间件使用
Usefunc (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {} -
中间件类型
type HandlerFunc func(*Context) -
Gin 框架已经使用 painc 、recover 使用了一个 Recovery 中间件
自定义中间件
创建 middleware 目录存放中间件
栗子:在访问系统,需要登录授权,携带 token 才能访问
定义 AuthRequired 中间件
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
)
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token, _ := c.GetQuery("token")
fmt.Println("auth check token..." + token)
c.Next()
}
}
- gin 在执行完这个中间,需要调用 Next() 方法,标示执行下一个中间件
- 如果中间件处理业务出现异常,可以使用 Abort(),终止掉,不再执行后面的逻辑,立即返回
使用中间件
- 在 main.go 中添加
app.Use(middleware.AuthRequired())
-
控制台会打印出
数据格式问题
现在流行前后端分离,那么前端在调用后端接口,返回的格式必须需要统一
在处理业务异常时,主动抛出异常,方便排查
自定义一个全局异常捕获
catch_error.go
package middleware
import (
"encoding/json"
"log"
"moose-go/api"
"moose-go/common"
"net/http"
"github.com/gin-gonic/gin"
)
func CatchError() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
url := c.Request.URL
method := c.Request.Method
log.Printf("| url [%s] | method | [%s] | error [%s] |", url, method, err)
var exception api.Exception
err := json.Unmarshal([]byte(string(err.(string))), &exception)
if err != nil {
common.Failed(c, http.StatusBadRequest, "未知错误,请联系管理员!")
c.Abort()
return
}
// 没有定义
errorMessage, ok := api.StatusText(exception.Code)
if !ok {
errorMessage = "系统异常"
}
common.Failed(c, exception.Code, errorMessage)
c.Abort()
}
}()
c.Next()
}
}
exception.go
package api
import (
"encoding/json"
"log"
)
type Exception struct {
Code int `json:"code"`
}
func NewException(code int) string {
e := Exception{Code: code}
data, err := json.Marshal(e)
if err != nil {
log.Panic(err)
}
return string(data)
}
result_code.go
package api
const (
AddUserFail = 10001
)
var resultCodeText = map[int]string{
AddUserFail: "添加用户失败",
}
func StatusText(code int) (string, bool) {
message, ok := resultCodeText[code]
return message, ok
}
测试
- 判断在添加用户Service AddUser,如果用户名为 test,抛出异常
func (us *UserService) AddUser(userName string) {
if userName == "test" {
log.Panic(api.NewException(api.AddUserFail))
return
}
userInfo := model.UserInfo{
UserName: userName,
UserId: "1",
AccountId: "1",
AccountName: "JiangJing",
Gender: "1",
Phone: "15798980298",
Avatar: "https://www.gitee.com/shizidada",
Email: "jiangjing@163,com",
Address: "中国",
Description: "我是江景啊",
}
userDao := dao.UserDao{DbEngine: engine.GetOrmEngine()}
_, err := userDao.InsertUser(&userInfo)
log.Print(err)
if err != nil {
log.Panic(api.NewException(api.AddUserFail))
}
}