马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
在当代Web应用开辟中,同一和规范化的API相应格式对于前后端协作至关告急。本日,我们来探究怎样在Gin框架中计划一套既实用又易于维护的相应格式规范。
为什么须要同一的相应格式?
起首,让我们思索一个题目:为什么要同一API相应格式?
- 前后端协作服从:同等的相应格式让前端开辟者能以同一的方式处置惩罚服务端相应
- 错误处置惩罚简化:尺度化的错误码和消息便于同一处置惩罚各种非常环境
- 接口文档维护:规范化相应淘汰文档编写工作量
- 客户端适配:移动端或其他客户端可以复用类似的相应剖析逻辑
计划同一的相应布局
让我们从最底子的相应布局开始。在本文的示例项目中,我接纳了如下的相应布局:- type baseResponse struct {
- Code int `json:"code"`
- Msg string `json:"msg"`
- Data any `json:"data"`
- }
复制代码 这个布局包罗了三个根本元素:
- Code: HTTP状态码或业务状态码,表现哀求的实行效果
- Msg: 人类可读的消息,形貌哀求的实行状态。(吐槽一下,见过各种项目,有的用"message", 有的用""messages", 调用方稍不注意就写错了,以是干脆用缩写)
- Data: 实际的业务数据,根据差别接口返回差别内容
有的业务还须要返回timestamp大概trace_id之类的内容,可以根据实际需求来修改。
实现相应处置惩罚工具类
有了底子布局后,我们可以构建一个相应处置惩罚工具类。在我的项目中,pkg/response/response.go 文件实现了多种常用的相应方法:- // Success 返回200状态码, 默认返回成功
- func Success(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "success",
- }
- }
- c.JSON(http.StatusOK, baseResponse{
- Code: http.StatusOK,
- Msg: opts.Msg,
- Data: data,
- })
- }
复制代码 通过这种方式,我们可以针对差别的HTTP状态码提供专门的相应方法:
- Success: 正常业务相应
- BadRequest: 参数校验失败
- Unauthorized: 权限校验失败
- NotFound: 资源不存在
- InternalServerError: 服务器内部错误
处置惩罚非常环境
仅仅处置惩罚正常的业务相应是不敷的,我们还须要同一拦截非常举行处置惩罚,否则非常和未注册路由都不会返回我们须要的格式。这里我用一个自界说的非通例复中心件做非常捕获:- func CustomRecovery() gin.HandlerFunc {
- return func(c *gin.Context) {
- defer func() {
- if err := recover(); err != nil {
- var brokenPipe bool
- // 检测是否是连接中断
- if ne, ok := err.(*net.OpError); ok {
- if se, ok := ne.Err.(*os.SyscallError); ok {
- if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
- strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
- brokenPipe = true
- }
- }
- }
- // 获取堆栈信息
- stack := string(debug.Stack())
- if brokenPipe {
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "network abort"})
- return
- }
- slog.Error("exception catched", "error", err, "stack", stack)
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "server internal error"})
- }
- }()
- c.Next()
- }
- }
复制代码 这个中心件有几个亮点:
- 毗连停止处置惩罚:特别处置惩罚了 "broken pipe" 和 "connection reset by peer" 错误,克制客户端提前断开毗连时产生冗余错误日记
- 错误信息记载:记载错误详情和堆栈信息,便于题目排查
- 同一错误相应:全部非常都以同一格式返回给客户端
路由未找到处置惩罚
除了非常处置惩罚,我们还须要处置惩罚哀求路由不存在的环境:- r.NoRoute(func(c *gin.Context) {
- response.NotFound(c, nil, &response.ResponseOption{
- Msg: "接口不存在",
- })
- })
复制代码 如许,当用户哀求不存在的接口时,也会收到格式同一的相应。
利用示例
在实际利用中,我们的控制器代码变得轻便明白:- r.GET("/a1", func(c *gin.Context) {
- response.Success(c, nil, nil)
- })
- r.GET("/a2", func(c *gin.Context) {
- var respData = struct {
- Name string
- }{
- Name: "hello",
- }
- response.Success(c, respData, &response.ResponseOption{
- Msg: "how a successful response",
- })
- })
复制代码 无论是在乐成相应还是错误相应中,客户端收到的都是类似格式的JSON数据,极大地提升了开辟体验。
增补-完备代码示例
项目布局:- ├── go.mod
- ├── go.sum
- ├── main.go
- └── pkg
- └── response
- └── response.go
复制代码 相应类
- package response
- import (
- "net/http"
- "github.com/gin-gonic/gin"
- )
- type baseResponse struct {
- Code int `json:"code"`
- Msg string `json:"msg"`
- Data any `json:"data"`
- }
- type ResponseOption struct {
- Msg string `json:"msg"`
- }
- // Success 返回200状态码, 默认返回成功
- func Success(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "success",
- }
- }
- c.JSON(http.StatusOK, baseResponse{
- Code: http.StatusOK,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // SuccessCreated 返回201状态码, 表示创建成功。常用于新增数据
- func SuccessCreated(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "success",
- }
- }
- c.JSON(http.StatusCreated, baseResponse{
- Code: http.StatusCreated,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // BadRequest 返回400错误, 常用于参数校验失败
- func BadRequest(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "bad request",
- }
- }
- c.JSON(http.StatusBadRequest, baseResponse{
- Code: http.StatusBadRequest,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // Unauthorized 401错误, 常用于权限校验失败
- func Unauthorized(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "unauthorized",
- }
- }
- c.JSON(http.StatusUnauthorized, baseResponse{
- Code: http.StatusUnauthorized,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // Forbidden 403错误, 常用于权限不足
- func Forbidden(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "forbidden",
- }
- }
- c.JSON(http.StatusForbidden, baseResponse{
- Code: http.StatusForbidden,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // NotFound 404错误, 常用于资源不存在
- func NotFound(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "not found",
- }
- }
- c.JSON(http.StatusNotFound, baseResponse{
- Code: http.StatusNotFound,
- Msg: opts.Msg,
- Data: data,
- })
- }
- func MethodNotAllowed(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "method not allowed",
- }
- }
- c.JSON(http.StatusMethodNotAllowed, baseResponse{
- Code: http.StatusMethodNotAllowed,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // UnprocessableEntity 422错误, 常用于客户端参数导致业务逻辑处理异常
- func UnprocessableEntity(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "unprocessable entity",
- }
- }
- c.JSON(http.StatusUnprocessableEntity, baseResponse{
- Code: http.StatusUnprocessableEntity,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // InternalServerError 500错误, 常用于服务器内部错误
- func InternalServerError(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "internal server error",
- }
- }
- c.JSON(http.StatusInternalServerError, baseResponse{
- Code: http.StatusInternalServerError,
- Msg: opts.Msg,
- Data: data,
- })
- }
复制代码 步伐入口
- package main
- import (
- "log/slog"
- "net"
- "net/http/httputil"
- "os"
- "runtime/debug"
- "strings"
- "tmpgo/pkg/response"
- "github.com/gin-gonic/gin"
- )
- func main() {
- gin.SetMode(gin.ReleaseMode) // 生产环境设为 ReleaseMode
- r := gin.New() // 不要用 gin.Default()
- // 添加 Logger 和 Recovery 中间件
- r.Use(gin.Logger())
- r.Use(CustomRecovery()) // 使用自定义异常恢复中间件
- // 注册路由
- r.GET("/a1", func(c *gin.Context) {
- response.Success(c, nil, nil)
- })
- r.GET("/a2", func(c *gin.Context) {
- var respData = struct {
- Name string
- }{
- Name: "hello",
- }
- response.Success(c, respData, &response.ResponseOption{
- Msg: "how a successful response",
- })
- })
- r.GET("/b1", func(c *gin.Context) {
- response.UnprocessableEntity(c, nil, nil)
- })
- r.GET("/b2", func(c *gin.Context) {
- panic("panic something")
- })
- // 设置自定义 404 处理
- r.NoRoute(func(c *gin.Context) {
- response.NotFound(c, nil, nil)
- })
- // 设置自定义 405 处理(方法不允许)
- r.NoMethod(func(c *gin.Context) {
- response.MethodNotAllowed(c, nil, nil)
- })
- r.Run("127.0.0.1:10000")
- }
- // 在正式项目中,可以统一放到中间件的模块中
- // CustomRecovery 自定义异常恢复中间件
- func CustomRecovery() gin.HandlerFunc {
- return func(c *gin.Context) {
- defer func() {
- if err := recover(); err != nil {
- var brokenPipe bool
- // 检测是否是连接中断
- if ne, ok := err.(*net.OpError); ok {
- if se, ok := ne.Err.(*os.SyscallError); ok {
- if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
- strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
- brokenPipe = true
- }
- }
- }
- // 获取堆栈信息
- stack := string(debug.Stack())
- // 获取原始请求内容(可选,方便排查是哪个参数导致的崩溃)
- httpRequest, _ := httputil.DumpRequest(c.Request, false)
- if brokenPipe {
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "network abort"})
- return
- }
- slog.Error("exception catched", "error", err, "stack", stack, "request", string(httpRequest))
- // c.AbortWithStatusJSON()
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "server internal error"})
- }
- }()
- c.Next()
- }
- }
复制代码 调用示例
- $ curl -X GET http://127.0.0.1:10000/a1
- {"code":200,"msg":"success","data":null}
- $ curl http://127.0.0.1:10000/a1
- {"code":200,"msg":"success","data":null}
- $ curl http://127.0.0.1:10000/a2
- {"code":200,"msg":"how a successful response","data":{"Name":"hello"}}
- $ curl http://127.0.0.1:10000/a3
- {"code":404,"msg":"not found","data":null}
- $ curl http://127.0.0.1:10000/b1
- {"code":422,"msg":"unprocessable entity","data":null}
- $ curl http://127.0.0.1:10000/b2
- {"code":500,"msg":"server internal error","data":null}
复制代码 |