在软件开发过程中,通过定义统一返回值还可以提高框架的可用性和可维护性,让开发人员更容易理解和使用框架中的各种功能。同时统一异常处理能够帮助我们更好地控制程序的流程,能够让我们更好地捕获异常并作出相应的处理。这样做可以帮助我们减少代码重复,提高代码的可读性和可维护性。
统一返回值
我们规范约定返回值参数有利于我们对数据进行管理以及提升前后端开发的效率。
在 项目根目录/src/main/ 目录下新建一个common/app 层级目录,并在 app目录下分别建立 code.go、msg.go、 response.go 用于存放 返回值、返回消息、统一返回值的实体对象。
code.go 主要定义返回值常量,代码如下:
1 2 3 4 5 6 7 8 9 10
| package app
import "net/http"
const ( SUCCESS = http.StatusOK ERROR = -1 )
|
msg.go 主要定义返回值常量对应的消息内容,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package app
var MessageMap = map[int]string{ SUCCESS: "成功", ERROR: "失败", }
func GetMsg(code int) string { msg, ok := MessageMap[code] if ok { return msg }
return MessageMap[ERROR] }
|
response.go 主要定义返回值的对象,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package app
import ( "github.com/gin-gonic/gin" "net/http" )
type Response struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` }
func Res(c *gin.Context, httpCode, errCode int, data interface{}) { c.JSON(httpCode, Response{ Code: errCode, Msg: GetMsg(errCode), Data: data, }) return }
func Success(c *gin.Context, data interface{}) { Res(c, http.StatusOK, SUCCESS, data) }
func Error(c *gin.Context, data interface{}) { Res(c, http.StatusOK, ERROR, data) }
|
这时我们可以修改 src/main/routers/api/health.go 中返回的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package api
import ( "github.com/gin-gonic/gin" "star-im/src/main/common/app" )
func Ping(c *gin.Context) { app.Success(c, nil) }
|
这时候我们通过浏览器访问 http://localhost:8081/ping,得到如下返回:
1 2 3 4 5
| { "code":200, "msg":"成功", "data":null, }
|
这时候我们就可以根据不同的返回值进行不同的业务处理了
统一异常处理
我们需要统一处理系统的异常信息并让异常结果也显示为统一的返回结果对象,那么需要进行统一异常处理。
在 项目根目录/src/main/ 目录下新建一个 handler 目录,并在目录下新建一个 exception.go 文件,用于处理异常信息
exception.go 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package handler
import ( "github.com/gin-gonic/gin" "log" "net/http" "runtime/debug" "star-im/src/main/common/app" )
func Recover(c *gin.Context) { defer func() { r := recover() if r != nil { log.Printf("panic: %v\n", r) debug.PrintStack() c.JSON(http.StatusOK, app.Response{ Code: app.ERROR, Msg: ErrorToString(r), Data: nil, }) c.Abort() } }()
c.Next() }
func ErrorToString(r interface{}) string { switch v := r.(type) { case error: return v.Error() default: return r.(string) } }
|
该类主要捕获panic异常,并返回 json 信息给客户端
在 src/main/routers/router.go 文件中添加如下代码即可。
此时我们可以修改router中 /ping 的方法来测试结果
将 src/main/routers/router.go 中如下代码
1
| r.GET("/ping", api.Ping)
|
修改为:
1 2 3 4 5
| r.GET("/ping", func(c *gin.Context) { var slice = []int{1, 2, 3, 4, 5} slice[6] = 6 })
|
然后重启项目,通过浏览器访问 http://localhost:8081/ping,得到如下返回:
1 2 3 4 5
| { "code":-1, "msg":"runtime error: index out of range [6] with length 5", "data":null, }
|
现在我们得到的就是统一的异常返回值,这里的 msg 可以根据需要再进行修改。
在业务中我们可以通过判断业务逻辑,再进行抛出异常,如将刚才函数中的内容修改为如下代码:
1 2 3 4 5 6
| r.GET("/ping", func(c *gin.Context) { if true { panic("抛出了指定的异常信息") } })
|
通过浏览器访问 http://localhost:8081/ping,得到如下返回:
1 2 3 4 5
| { "code":-1, "msg":"抛出了指定的异常信息", "data":null, }
|
可以看到成功捕获了 panic 抛出的异常信息,这可以很方便我们做业务逻辑的时候处理异常。(测试成功后记得把ping的函数恢复回之前的)
gin router 中也提供了针对 NoRoute 和 NoMethod 的处理,在 router.go 中添加
1 2 3
| r.NoRoute(handler.HandleNotFound) r.NoMethod(handler.HandleNotFound)
|
在 src/main/handler/exception.go 中添加处理方法,直接返回处理结果即可
1 2 3 4 5 6 7 8 9 10 11
| func HandleNotFound(c *gin.Context) { app.ErrorWithCode(c, app.NOT_FOUND, nil) return }
|
swag 接口文档
Swagger是一种API框架,它可以为REST APIs定义、生成、测试和文档化。它使用一种名为Swagger元数据的特殊格式来描述API,并使用Swagger UI来展示API的定义。这使得开发人员可以在不离开API文档的情况下测试API,并且可以轻松地为API创建文档。
我们引入 Swagger 来生成接口文档,方便统一管理接口及调试。
相关链接:
GitHub
安装
1
| go install github.com/swaggo/swag/cmd/swag@latest
|
使用
1
| swag init -o "src/main/docs"
|
- -o 为 output,指定输出目录,默认为“./docs”
其他更多文章参考 GitHub中文文档
在主程序入口 main.go 中可以添加如下注释:
在 src/main/routers/api/health.go 文件中添加如下注释
1 2 3 4 5 6 7 8 9
|
func Ping(c *gin.Context) { …… }
|
在 src/main/routers/router.go 加入swagger接口文档的访问,并引入指定了目录的swagger文件
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package routers
import ( "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "io" "os" _ "star-im/src/main/docs" "star-im/src/main/handler" "star-im/src/main/routers/api" )
func Setup() *gin.Engine { r := gin.Default() f, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(f)
r.Use(handler.Recover) r.Use(gin.Logger())
r.GET("/ping", api.Ping)
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) return r }
|
主要变更内容为
import _ "star-im/src/main/docs"import swaggerFiles "github.com/swaggo/files"import ginSwagger "github.com/swaggo/gin-swagger"r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
测试
通过浏览器访问 http://localhost:8081/swagger/index.html#/ 可以进入到swagger 接口文档的管理界面
至此,当前目录结构为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| . ├── LICENSE ├── README.md ├── doc │ └── build │ └── 1-framwork.md ├── gin.log ├── go.mod ├── go.sum ├── main.go └── src ├── main │ ├── common │ │ └── app │ │ ├── code.go │ │ ├── msg.go │ │ └── response.go │ ├── config │ │ ├── database │ │ │ └── database.go │ │ ├── init.go │ │ └── settings │ │ └── settings.go │ ├── docs │ │ ├── docs.go │ │ ├── swagger.json │ │ └── swagger.yaml │ ├── handler │ │ └── exception.go │ ├── models │ │ └── model.go │ ├── routers │ │ ├── api │ │ │ ├── health.go │ │ │ └── v1 │ │ └── router.go │ └── util ├── resource │ └── app.yml └── test └── pkg ├── test_gin.go ├── test_gorm.go ├── test_jwt.go ├── test_redis.go └── test_viper.go
|