// Package api_doc ... // // Description : api_doc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2025-02-16 13:19 package api_doc import ( "embed" "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "github.com/gin-gonic/gin" knife4goFiles "github.com/go-webtools/knife4go" knife4goGin "github.com/go-webtools/knife4go/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "net/http" "strings" ) //go:embed ydoc-lucky-ui/* var ydocUIFiles embed.FS //go:embed redoc-free/index.html var redocFreeIndexContent string // NewSwaggerUI ... // // Author : go_developer@163.com<白茶清欢> // // Date : 21:21 2025/2/15 func NewSwaggerUI(info *define.Info, servers []*define.ServerItem, uiTheme string) *SwaggerUI { return &SwaggerUI{ docInstance: NewOpenapiDoc(info, servers), uiTheme: uiTheme, } } type SwaggerUI struct { docInstance *Generate // 文档实例 uiTheme string // 文档主题, swaggerUI / knife4go, 默认 knife4go router *gin.Engine baseUri string } // DocInstance 文档实例 // // Author : go_developer@163.com<白茶清欢> // // Date : 13:23 2025/2/16 func (su *SwaggerUI) DocInstance() *Generate { return su.docInstance } // RegisterHandler ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:00 2025/2/16 func (su *SwaggerUI) RegisterHandler(router *gin.Engine, baseUri string) { su.router = router baseUri = strings.TrimRight(baseUri, "/") if len(baseUri) == 0 { baseUri = "/docs/swagger" } su.baseUri = baseUri router.GET(baseUri+"/*any", func(ctx *gin.Context) { if ctx.Request.RequestURI == baseUri+"/doc.json" { // 默认swagger, 通过此接口读取文档数据 ctx.JSON(http.StatusOK, su.docInstance.Doc()) ctx.Abort() } if ctx.Request.RequestURI == "/doc/swagger/openapi.json" { // knife4go 文档通过此接口读取文档列表 ctx.JSON(http.StatusOK, []map[string]any{ { "name": "服务文档", "url": "doc.json", "swaggerVersion": consts.SwaggerDocVersion3, }, }) ctx.Abort() } }, su.Handler()) router.GET("/swagger-resources", func(ctx *gin.Context) { // lucky UI获取分组信息 ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 允许访问所有域 ctx.JSON(http.StatusOK, []map[string]any{ { "name": "服务文档", "url": baseUri + "/doc.json", "swaggerVersion": consts.SwaggerDocVersion3, }, }) // ctx.JSON(http.StatusOK, swaggerInstance.docInstance.Data()) }) } // Handler 访问文档的接口处理 // // Author : go_developer@163.com<白茶清欢> // // Date : 21:34 2025/2/15 func (su *SwaggerUI) Handler() func(ctx *gin.Context) { switch su.uiTheme { case define.SwaggerUIThemeKnife4go: return su.HandleKnife4goUI() case define.SwaggerUIThemeYDocLucky: // YDoc-Lucky-UI 主题处理 return su.HandleLuckyUI() case define.SwaggerUIThemeDefault: return su.HandleSwaggerUI() case define.SwaggerUIThemeRedocFree: // redoc免费版 return su.HandleRedocFreeUI() default: return su.HandleSwaggerUI() } } // HandleLuckyUI ... // // Author : go_developer@163.com<白茶清欢> // // Date : 16:04 2025/2/16 func (su *SwaggerUI) HandleLuckyUI() func(ctx *gin.Context) { // su.router.StaticFS(su.baseUri+"/assets", http.FS(ydocUIFiles)) return func(ctx *gin.Context) { fileRealPath := strings.TrimPrefix(ctx.Request.RequestURI, su.baseUri) byteData, _ := ydocUIFiles.ReadFile(define.SwaggerUIThemeYDocLucky + fileRealPath) if strings.HasSuffix(ctx.Request.RequestURI, "html") { byteData = []byte(strings.ReplaceAll(string(byteData), "{{BASE_URI}}", su.baseUri)) } uriArr := strings.Split(ctx.Request.RequestURI, ".") contentType := "text/" + uriArr[len(uriArr)-1] if strings.HasSuffix(ctx.Request.RequestURI, "png") { contentType = "image/png" } else if strings.HasSuffix(ctx.Request.RequestURI, "js") { contentType = "application/javascript" } ctx.Header("Content-Type", contentType) ctx.String(http.StatusOK, string(byteData)) ctx.Abort() } } // HandleKnife4goUI ... // // Author : go_developer@163.com<白茶清欢> // // Date : 21:38 2025/2/15 func (su *SwaggerUI) HandleKnife4goUI() func(ctx *gin.Context) { resetOption := func(cfg *knife4goGin.Config) { if nil == cfg { return } cfg.Title = "服务接口文档" } return knife4goGin.WrapHandler(knife4goFiles.Handler, resetOption) } // HandleSwaggerUI ... // // Author : go_developer@163.com<白茶清欢> // // Date : 21:41 2025/2/15 func (su *SwaggerUI) HandleSwaggerUI() func(ctx *gin.Context) { return ginSwagger.WrapHandler(swaggerFiles.Handler) } // HandleRedocFreeUI 处理redoc_free主题 // // Author : go_developer@163.com<白茶清欢> // // Date : 16:40 2025/2/18 func (su *SwaggerUI) HandleRedocFreeUI() func(ctx *gin.Context) { return func(ctx *gin.Context) { // TODO : 这部分数据支持外部传参替换 replaceTable := map[string]string{ "{{DOC_TITLE}}": fmt.Sprintf("【%v】%v", su.docInstance.Doc().Info.Version, su.docInstance.Doc().Info.Title), "{{CSS_FAMILY}}": "https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700", "{{DOC_PATH}}": "doc.json", "{{REDOC_STANDALONE_JS}}": "https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js", } for k, v := range replaceTable { redocFreeIndexContent = strings.ReplaceAll(redocFreeIndexContent, k, v) } ctx.Header("Content-Type", "text/html; charset=utf-8") ctx.String(http.StatusOK, redocFreeIndexContent) ctx.Abort() } }