swag
swaggo/swag là một triển khai của Swagger API 2.0 trong ngôn ngữ Go, thông qua việc viết chú thích theo định dạng chỉ định có thể tạo tài liệu API kiểu swagger.json và swagger.yaml, thuận tiện cho việc xuất và nhập.
Repository: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)
Tài liệu: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)
swag mặc định hỗ trợ các framework web sau đây, bài viết này lấy gin làm ví dụ, để trình bày ví dụ gin kết hợp swagger nhanh chóng tạo tài liệu API.
TIP
Nếu không quen với cú pháp swagger, có thể đến About Swagger Specification | Documentation | Swagger
Cài đặt
Đầu tiên tải công cụ dòng lệnh swagger
go install github.com/swaggo/swag/cmd/swag@latestSau đó tải phụ thuộc mã nguồn swagger
go get github.com/swaggo/swagTIP
Để tránh gặp vấn đề, phiên bản của cả hai phải giữ nhất quán.
Sau đó tải thư viện file tĩnh của swagger, html, css, js, v.v., đều được nhúng vào mã Go.
go get github.com/swaggo/files@latestCuối cùng tải thư viện thích ứng gin của swagger
go get github.com/swaggo/gin-swagger@latestVì bài viết này chỉ dùng gin làm ví dụ, các adapter framework web khác vui lòng tự tìm hiểu, về cơ bản đều tương tự nhau.
Sử dụng
Sử dụng go mod để tạo một dự án Go cơ bản nhất, tạo main.go, viết nội dung sau.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @BasePath /api/v1
func main() {
engine := gin.Default()
engine.GET("/api/v1/ping", Ping)
engine.Run(":80")
}
// Ping godoc
// @Summary say hello world
// @Description return hello world json format content
// @param name query string true "name"
// @Tags system
// @Produce json
// @Router /ping [get]
func Ping(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
})
}Đây là một ví dụ web gin rất đơn giản, chú thích trên hàm main là thông tin cơ bản của tài liệu, hàm Ping là một interface bình thường. Tiếp theo thực thi lệnh để tạo tài liệu, mặc định là trong thư mục docs cùng cấp với main.go
swag initSửa đổi mã main.go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
// Import ẩn danh gói tài liệu API đã tạo
_ "golearn/docs"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @BasePath /api/v1
func main() {
engine := gin.Default()
// Đăng ký route file tĩnh swagger
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
engine.GET("/api/v1/ping", Ping)
engine.Run(":80")
}
// Ping godoc
// @Summary say hello world
// @Description return hello world json format content
// @param name query string true "name"
// @Tags system
// @Produce json
// @Router /ping [get]
func Ping(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
})
}Chạy chương trình, truy cập 127.0.0.1/swagger/index.html, giao diện như sau

Như vậy là đã chạy một tài liệu API cơ bản. Tiếp theo ngoài một số điểm đặc biệt cần lưu ý, về cơ bản không có khác biệt quá lớn so với việc sử dụng các ngôn ngữ khác.
Tham số
Định dạng định nghĩa tham số là
@param name paramtype datatype isRequired commentMột ví dụ như sau
@param userId query int true "user unique id"Trong đó các loại tham số được hỗ trợ là
- query
- path
- header
- body
- formData
Các kiểu dữ liệu là
- string (string)
- integer (int, uint, uint32, uint64)
- number (float32)
- boolean (bool)
- user defined struct
Kiểu tham số cũng có thể là kiểu của bạn, miễn là có thể bị swagger quét đến.
Phản hồi
Định dạng cơ bản để định nghĩa phản hồi interface như sau
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPErrorGồm mã trạng thái, kiểu cơ bản, kiểu dữ liệu. {array} biểu thị là một mảng, sẽ hiển thị dưới dạng mảng của kiểu dữ liệu, {object} sẽ hiển thị dưới dạng gốc của kiểu dữ liệu. Ví dụ thông thường chúng ta sẽ định nghĩa một body phản hồi thống nhất
type JSONResult struct {
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
}Kiểu của trường Data không xác định, khi mô tả ví dụ phản hồi, có thể kết hợp nó, như sau
// Kết hợp
@success 200 {object} jsonresult.JSONResult{data=Account} "desc"
// Mảng
@success 200 {object} jsonresult.JSONResult{data=[]Account} "desc"Model
Thêm chú thích cho trường struct sẽ bị swagger quét thành chú thích trường model
package model
type Account struct {
// account id
ID int `json:"id" example:"1"`
// username
Name string `json:"name" example:"account name"`
}Trong đó giá trị của tag example sẽ được hiển thị làm giá trị ví dụ trong trang, tất nhiên nó cũng hỗ trợ giới hạn trường
type Foo struct {
Bar string `minLength:"4" maxLength:"16"`
Baz int `minimum:"10" maximum:"20" default:"15"`
Qux []string `enums:"foo,bar,baz"`
}Tất cả các model khi sử dụng đều phải đảm bảo có thể bị swagger quét đến, nếu không sẽ không có tác dụng.
Xác thực
Phần xác thực hỗ trợ
- Basic Auth
- API Key
- OAuth2 app auth
- OAuth2 implicit auth
- OAuth2 password auth
- OAuth2 access code auth
Giả sử xác thực interface dùng JWT, lưu trữ trong trường Authorization của header, chúng ta có thể định nghĩa như sau
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
Về bản chất đây chỉ là một apikey, nếu bạn truyền vào bearer token thì cần tự thêm tiền tố Bearer.

Sau đó thêm chú thích sau vào interface cần xác thực
// @security BearerGiá trị của nó là tên được định nghĩa trong securityDefinitions của bạn.
Cấu hình
swag thực chất là lưu trữ nhiều instance swagger khác nhau trong một map, ginSwagger làm adapter đọc doc.json từ instance, tức là file định nghĩa API interface, swaggerFiles cung cấp file HTML tĩnh để hiển thị trang web, phân tích định nghĩa API và tạo giao diện, sau khi hiểu toàn bộ quy trình, có thể thực hiện các thao tác tùy chỉnh.
// Name là một tên duy nhất được sử dụng để đăng ký instance swag.
// Tên instance mặc định
const Name = "swagger"
var (
swaggerMu sync.RWMutex
// Bảng instance
swags map[string]Swagger
)func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc {
var once sync.Once
if config.InstanceName == "" {
config.InstanceName = swag.Name
}
if config.Title == "" {
config.Title = "Swagger UI"
}
// tạo template với tên
index, _ := template.New("swagger_index.html").Parse(swaggerIndexTpl)
var matcher = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[?|.]*`)
return func(ctx *gin.Context) {
if ctx.Request.Method != http.MethodGet {
ctx.AbortWithStatus(http.StatusMethodNotAllowed)
return
}
// Khớp route
matches := matcher.FindStringSubmatch(ctx.Request.RequestURI)
if len(matches) != 3 {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
return
}
path := matches[2]
once.Do(func() {
handler.Prefix = matches[1]
})
switch filepath.Ext(path) {
case ".html":
ctx.Header("Content-Type", "text/html; charset=utf-8")
case ".css":
ctx.Header("Content-Type", "text/css; charset=utf-8")
case ".js":
ctx.Header("Content-Type", "application/javascript")
case ".png":
ctx.Header("Content-Type", "image/png")
case ".json":
ctx.Header("Content-Type", "application/json; charset=utf-8")
}
switch path {
// Trang chính
case "index.html":
_ = index.Execute(ctx.Writer, config.toSwaggerConfig())
// File mô tả API
case "doc.json":
doc, err := swag.ReadDoc(config.InstanceName)
if err != nil {
ctx.AbortWithStatus(http.StatusInternalServerError)
return
}
ctx.String(http.StatusOK, doc)
default:
handler.ServeHTTP(ctx.Writer, ctx.Request)
}
}
}Thông qua mã Go được tạo để tự động hoàn thành việc đăng ký instance, dưới đây là một phần mã docs.go được tự động tạo
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
// Đăng ký instance
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}Có thể thấy trong hàm init có một hàm Register dùng để đăng ký instance hiện tại, nếu muốn sửa đổi tên instance không khuyến nghị chỉnh sửa trong file này, vì file docs.go được tự động tạo, chỉ cần sử dụng tham số --instanceName appapi khi tạo mã. Để thuận tiện, có thể sử dụng lệnh go generate nhúng vào file Go, thuận tiện cho việc tự động tạo mã, như sau.
// swagger declarative api comment
// @title App Internal API Documentation
// @version v1.0.0
// @description Wilson api documentation
// @BasePath /api/v1
//go:generate swag init --generatedTime --instanceName appapi -g api.go -d ./ --output ./swaggerCá nhân không thích viết chú thích thông tin chung swagger trong main.go hoặc trên hàm main, viết những chú thích này ở phía trên go generate là phù hợp nhất.
TIP
Nếu cần nhiều instance,务必 giữ tên instance duy nhất, nếu không sẽ panic
Để tùy chỉnh một số cấu hình, cần sử dụng ginSwagger.CustomWrapHandler, nó so với cái trước có thêm một tham số Config, ý nghĩa như sau
// Config stores ginSwagger configuration variables.
type Config struct {
// URL trỏ đến định nghĩa API (thường là swagger.json hoặc swagger.yaml). Mặc định là `doc.json`.
URL string
// Trạng thái mở rộng danh sách interface
DocExpansion string
// Tên instance
InstanceName string
// Tiêu đề
Title string
// Độ sâu mở rộng
DefaultModelsExpandDepth int
// Như tên gọi
DeepLinking bool
PersistAuthorization bool
Oauth2DefaultClientID string
}Sử dụng swaggerFiles.NewHandler() để thay thế Handler mặc định, đặc biệt khi có nhiều instance.
engine.GET(openapi.ApiDoc, ginSwagger.CustomWrapHandler(openapi.Config, swaggerFiles.NewHandler()))Ngoài ra còn có thể thực hiện một loạt các thao tác như ghi đè kiểu, đều khá đơn giản, nội dung khác có thể đọc tài liệu chính thức.
Lưu ý
swag tạo file mô tả API openapi dựa trên chú thích, khi tạo, thư mục được chỉ định phải chứa thông tin cơ bản của tài liệu API, mặc định tìm kiếm trong
main.goswag initmặc định chỉ định thư mục hiện tại, giá trị là./, có thể sử dụngswag init -dđể chỉ định nhiều thư mục, sử dụng dấu phẩy phân cách, thư mục được chỉ định đầu tiên phải chứa thông tin cơ bản của tài liệu API. Ví dụswag init -d ./,./api-g, file lưu trữ thông tin cơ bản của tài liệu API có thể tùy chỉnh tên file, mặc định làmain.go, khi tạo tài liệu, sử dụng tham số-gđể chỉ định tên fileswag init -g api.go -d ./,./apiÝ nghĩa của lệnh này là phân tích thông tin cơ bản của tài liệu API trong
./api.go, đồng thời tìm kiếm và phân tích thông tin chú thích của các interface khác dưới hai thư mục./và./apivà tạo tài liệu tương ứng.Tham số
-ocó thể chỉ định đường dẫn đầu ra của file mô tả tài liệu, mặc định là./docs, ví dụ:swag init -o ./api/docs--otcó thể chỉ định kiểu file đầu ra, mặc định là (docs.go,swagger.json,swagger.yaml), nếu muốn sử dụng chương trình Go để tải swagger ui, file Go là không thể thiếu.swag init --ot go,yamlCác file json và yaml được tạo ra có thể thuận tiện cho việc nhập dữ liệu trên các phần mềm quản lý API khác.
Viết chú thích ở đâu cũng được,就算 không viết trên hàm cũng có thể phân tích, chỉ là viết trên hàm có tính đọc tốt hơn, về bản chất vẫn là một DSL được nhúng cùng với mã nguồn Go dưới dạng chú thích.
swag còn hỗ trợ nhiều tham số khác, có thể sử dụng
swag init -hđể xem.
