Gin 如何動態生成模型 swagger 文件

邱佳飛發表於2021-12-01

在做 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 http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://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"`
}

相關文章