Gin 如何动态生成模型 swagger 文档

在做 API 接口开发时, 一般会统一 API 返回格式, 例如

{
    "code": 200,
    "data": {
        //xxxxx
        //xxxxx
    },
    "message": "OK"
}

在后端代码定义中, 也会定义一个结构体来对应这种结构, 并且, 由于 data 字段里的数据是未知的(与具体业务相关), 所以会定义一个 interface 来接收

type ApiResponse struct {
	Code int         `json:"code"`
	Msg  string      `json:"message"`
	Data interface{} `json:"data"`
}

然后根据具体业务响应, 向 data 传入不同的模型, 比如

c.JSON(200, ApiResponse{200, "OK", User})

但是这里有个很大的问题, swagger 文档中, 这个接口的返回值该怎么定义?

// @Summary 获取用户信息
// ...
// ...
// @Success 200 {object} ApiResponse "ok"
func GetUser(c *gin.Context) {
    xxxx
}

如果这样定义, 生成的文档会是下面这样, 因为原始 ApiResponse 就是一个 interface, 所以是空

但是这样的文档写出来就没什么意义了, 大多数的做法就是会专门定义一个用于 swagger 显示的结构体, 类似这样

type UserResponse struct {
	Code int         `json:"code"`
	Msg  string      `json:"message"`
	Data User        `json:"data"`
}

虽然效果有了, 但是这样无疑增加了巨多的工作量, 让写代码变得索然无味, 翻看 swaggo/swag 的文档, 发现支持了替换字段的方式, 可以完美解决现在这种问题, 效果如下

下面是测试代码

package main

import (
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService //swagger.io/terms/

// @contact.name API Support
// @contact.url //www.swagger.io/support
// @contact.email [email protected]

// @license.name Apache 2.0
// @license.url //www.apache.org/licenses/LICENSE-2.0.html

// @host petstore.swagger.io
// @BasePath /v2
func main() {
	r := gin.New()
	r.GET("/user/:id", GetUser)
}

// @Summary 获取用户信息
// @Description get User by ID
// @ID get-user-by-id
// @Accept  json
// @Produce  json
// @Param   id      path   int     true  "用户 id"
// @Success 200 {object} ApiResponse{data=User} "ok"
// @Router /user/{id} [get]
func GetUser(c *gin.Context) {
	resp := new(ApiResponse)

	paramID := c.Param("id")
	uid, _ := strconv.Atoi(paramID)
	user := User{
		ID:   uid,
		Name: "张三",
	}
	resp.Code = 200
	resp.Msg = "OK"
	resp.Data = user
	c.JSON(http.StatusOK, resp)
}

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

type ApiResponse struct {
	Code int         `json:"code"`
	Msg  string      `json:"message"`
	Data interface{} `json:"data"`
}
Tags: