feat: 迁移swaggerUI处理

This commit is contained in:
2026-01-08 10:06:18 +08:00
parent 2ad85fd950
commit b174a57b7f
21 changed files with 207 additions and 8 deletions

16
router/embed.go Normal file
View File

@@ -0,0 +1,16 @@
// Package router ...
//
// Description : theme ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-08-23 09:23
package router
import "embed"
//go:embed theme/ydoc-lucky-ui/*
var YdocUIFiles embed.FS
//go:embed theme/redoc-free/index.html
var RedocFreeIndexContent string

View File

@@ -16,7 +16,6 @@ import (
"strings"
"sync"
apiDoc "git.zhangdeman.cn/zhangdeman/api-doc"
apiDocDefine "git.zhangdeman.cn/zhangdeman/api-doc/define"
"git.zhangdeman.cn/zhangdeman/api-doc/enums"
"git.zhangdeman.cn/zhangdeman/api-doc/openapi"
@@ -173,7 +172,7 @@ func (s *server) RegisterDocHandler() {
})
ctx.Abort()
}
}, apiDoc.NewSwaggerUI(fmt.Sprintf("[%v]接口文档", s.option.docConfig.Flag), s.option.docConfig.BaseUri, enums.SwaggerUITheme(s.option.docConfig.UiTheme)).Handler())
}, NewSwaggerUI(fmt.Sprintf("[%v]接口文档", s.option.docConfig.Flag), s.option.docConfig.BaseUri, enums.SwaggerUITheme(s.option.docConfig.UiTheme)).Handler())
s.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{

113
router/swagger_ui.go Normal file
View File

@@ -0,0 +1,113 @@
// Package router ...
//
// Description : api_doc ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-16 13:19
package router
import (
"net/http"
"path/filepath"
"strings"
"git.zhangdeman.cn/zhangdeman/api-doc/enums"
"git.zhangdeman.cn/zhangdeman/api-doc/theme"
"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"
)
// NewSwaggerUI ...
func NewSwaggerUI(docTitle string, docBaseUri string, uiTheme enums.SwaggerUITheme) *SwaggerUI {
return &SwaggerUI{
baseUri: docBaseUri,
docTitle: docTitle,
uiTheme: uiTheme,
}
}
type SwaggerUI struct {
baseUri string
docTitle string
uiTheme enums.SwaggerUITheme // 文档主题, swaggerUI / knife4go, 默认 knife4go
}
// Handler 访问文档的接口处理
func (su *SwaggerUI) Handler() func(ctx *gin.Context) {
switch su.uiTheme {
case enums.SwaggerUIThemeKnife4go:
return su.HandleKnife4goUI()
case enums.SwaggerUIThemeYDocLucky:
// YDoc-Lucky-UI 主题处理
return su.HandleLuckyUI()
case enums.SwaggerUIThemeDefault:
return su.HandleSwaggerUI()
case enums.SwaggerUIThemeRedocFree:
// redoc免费版
return su.HandleRedocFreeUI()
default:
return su.HandleSwaggerUI()
}
}
// HandleLuckyUI ...
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, _ := theme.YdocUIFiles.ReadFile(filepath.Join(enums.SwaggerUIThemeYDocLucky.String(), 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(consts.HeaderKeyContentType.String(), contentType)
ctx.String(http.StatusOK, string(byteData))
ctx.Abort()
}
}
// HandleKnife4goUI ...
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 ...
func (su *SwaggerUI) HandleSwaggerUI() func(ctx *gin.Context) {
return ginSwagger.WrapHandler(swaggerFiles.Handler)
}
// HandleRedocFreeUI 处理redoc_free主题
func (su *SwaggerUI) HandleRedocFreeUI() func(ctx *gin.Context) {
return func(ctx *gin.Context) {
// TODO : 这部分数据支持外部传参替换
replaceTable := map[string]string{
"{{DOC_TITLE}}": su.docTitle,
"{{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 {
theme.RedocFreeIndexContent = strings.ReplaceAll(theme.RedocFreeIndexContent, k, v)
}
ctx.Header(consts.HeaderKeyContentType.String(), "text/html; charset=utf-8")
ctx.String(http.StatusOK, theme.RedocFreeIndexContent)
ctx.Abort()
}
}

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>{{DOC_TITLE}}</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700 -->
<!-- 预留占位符, 可以通过外部替换 -->
<link href="{{CSS_FAMILY}}" rel="stylesheet">
<!--
Redoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- 预留占位符, 可以通过外部替换 -->
<!--doc.json, 相对于外部设置好的base_url-->
<redoc spec-url='{{DOC_PATH}}'></redoc>
<!--https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js-->
<script src="{{REDOC_STANDALONE_JS}}"> </script>
</body>
</html>

View File

@@ -0,0 +1 @@
#titleDoc[data-v-bf5b8710]{font-size:21px}[data-v-bf5b8710] .too-old td{color:#ff231e!important}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{i as e,e as t,f as a,g as l,j as u,w as s,a3 as d,a4 as o,z as f,A as n,a5 as i,O as r}from"./vendor.08310185.js";const _={id:"typeCount"},p={id:"typeCount"},c={id:"typeCount"},b={id:"typeCount"},h={setup(h){let v=e("g"),y=v.value.data,g=v.value.resource,E=y.paths,k=Object.keys(E),j=0,m=0,x=0,C=0;return k.forEach((e=>{let t=E[e];Object.keys(t).forEach((e=>{switch(e){case"get":j+=1;break;case"post":m+=1;break;case"put":x+=1;break;case"delete":C+=1}}))})),(e,h)=>(t(),a("div",null,[l(u(d),{title:"基础信息"},{default:s((()=>[l(u(o),{"x-gap":12,"y-gap":20,cols:2},{default:s((()=>[l(u(i),null,{default:s((()=>[l(u(d),{title:"标题:",hoverable:""},{default:s((()=>[f(n(u(y).info.title),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"Host:",hoverable:""},{default:s((()=>[f(n(u(y).info.contact.url),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"简介:",hoverable:""},{default:s((()=>[f(n(u(y).info.description),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"base path:",hoverable:""},{default:s((()=>[f(n(u(y).basePath),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"作者:",hoverable:""},{default:s((()=>[f(n(u(y).info.contact.name),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"分组名称:",hoverable:""},{default:s((()=>[f(n(u(g)[0].url),1)])),_:1})])),_:1})])),_:1})])),_:1}),l(u(d),{title:"Api类型统计",style:{"margin-top":"30px"}},{default:s((()=>[l(u(o),{"x-gap":50,"y-gap":10,cols:4},{default:s((()=>[l(u(i),null,{default:s((()=>[l(u(d),{title:"POST",hoverable:""},{default:s((()=>[r("span",_,n(u(m)),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"GET",hoverable:""},{default:s((()=>[r("span",p,n(u(j)),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"PUT",hoverable:""},{default:s((()=>[r("span",c,n(u(x)),1)])),_:1})])),_:1}),l(u(i),null,{default:s((()=>[l(u(d),{title:"DELETE",hoverable:""},{default:s((()=>[r("span",b,n(u(C)),1)])),_:1})])),_:1})])),_:1})])),_:1})]))}};export{h as default};

View File

@@ -0,0 +1 @@
#typeCount{font-size:24px}

View File

@@ -0,0 +1 @@
import{r as e,u as a,o as t,e as l,y as s,w as r,j as i,T as o,a3 as u,f as n,g as d,ao as c,q as p,ap as g,t as y,v,x as f,aq as m,W as k,z as S,b as h,ar as _,as as x,O,A as I,aa as J}from"./vendor.08310185.js";const N={key:0},b=S("点我清除全部用例"),q=S(" 你确定要全部清理吗我会把LocalStorage全部干掉哦。 "),z={style:{"font-size":"18px"}},C={key:1},E={setup(S){let E=e([]),j=e([{title:"名称",key:"title"},{title:"状态",key:"status"},{title:"创建时间",key:"time"},{title:"请求参数",key:"param",render:e=>(e.param.forEach((e=>{delete e.in,delete e.type,delete e.required,delete e.active})),JSON.stringify(e.param))},{title:"请求体",key:"body"},{title:"操作",key:"action",render:e=>h(y,{type:"primary",onClick:()=>W(e)},{default:()=>"删除"})}]),P=e(!0);a();let W=e=>{let a=localStorage.getItem(e.group),t=JSON.parse(a);for(var l=0;l<t.length;l++)if(t[l].title==e.title){1==t.length?localStorage.removeItem(e.group):(t.splice(l,1),localStorage.setItem(e.group,JSON.stringify(t)));break}A()},w=()=>{localStorage.clear(),A()},A=()=>{P.vlaue=!1,E.value=[];for(var e=0;e<localStorage.length;e++){var a=localStorage.key(e);if(a.startsWith("lucky")){let e={},t=JSON.parse(localStorage.getItem(a));t.forEach((e=>{e.group=a})),e.key=a.split("_")[1],e.value=t,E.value.push(e)}}P.vlaue=!0};return t((()=>{A()})),(e,a)=>(l(),s(i(o),{vertical:""},{default:r((()=>[i(P)?(l(),s(i(u),{key:0,title:"请求用例管理"},{default:r((()=>[i(E).length>0?(l(),n("div",N,[d(i(o),{vertical:"",size:15},{default:r((()=>[d(i(c),{"positive-text":"好的,没关系.","negative-text":"Emmm,算了吧.",onPositiveClick:i(w)},{icon:r((()=>[d(i(p),null,{default:r((()=>[d(i(g))])),_:1})])),trigger:r((()=>[d(i(y),{dashed:"",type:"primary"},{default:r((()=>[b])),_:1})])),default:r((()=>[q])),_:1},8,["onPositiveClick"]),(l(!0),n(v,null,f(i(E),((e,a)=>(l(),n("div",null,[d(i(_),null,{default:r((()=>[d(i(x),{name:"index"},{header:r((()=>[O("span",z,I(e.key),1)])),default:r((()=>[d(i(J),{columns:i(j),data:e.value},null,8,["columns","data"])])),_:2},1024)])),_:2},1024)])))),256))])),_:1})])):(l(),n("div",C,[d(i(m),{status:"404",title:"好像没有多余的请求用例了",description:"去创建几个试试吧"})]))])),_:1})):k("",!0)])),_:1}))}};export{E as default};

View File

@@ -0,0 +1 @@
import{a as l}from"./index.4ac3587a.js";import{r as a,o as e,e as t,f as u,g as s,j as n,T as d,w as o,l as f,z as p,b as r,D as b}from"./vendor.08310185.js";const i=p(" asdsa "),y={setup(p){const y=a([{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"1"},{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"3"},{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"4"},{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"5"},{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"6"},{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"7"},{label:()=>r(b,null,{default:()=>"电灯熄灭 物换星移 泥牛入海"}),key:"8"},{label:()=>r(b,null,{default:()=>"黑暗好像 一颗巨石 按在胸口"}),key:"2"}]);return e((()=>{let a=window.location.pathname.replace("/doc.html","");a="http://127.0.0.1:8888",l({url:"http://127.0.0.1:8888/getMc",method:"get"}).then((l=>{l.data&&console.log(l.data)}))})),(l,a)=>(t(),u("div",null,[s(n(d),null,{default:o((()=>[s(n(f),{options:y.value,style:{width:"180px"},"default-value":"1"},null,8,["options"]),i])),_:1})]))}};export{y as default};

View File

@@ -0,0 +1 @@
import{e as s,y as t,w as a,j as e,a3 as o,g as u,aq as i,t as n,z as l}from"./vendor.08310185.js";const r=l("去提个Issues催一下"),c={setup(l){let c=()=>{window.open("https://github.com/NoBugBoy/YdocLuckyUi/issues/new")};return(l,d)=>(s(),t(e(o),null,{default:a((()=>[u(e(i),{status:"500",title:"作者太懒了,这个功能并不太想做",description:"也许你真的不太需要这个东西."},{footer:a((()=>[u(e(n),{onClick:e(c)},{default:a((()=>[r])),_:1},8,["onClick"])])),_:1})])),_:1}))}};export{c as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
<svg height="16" width="8" xmlns="http://www.w3.org/2000/svg">
<polygon points="0,0 8,8 0,16"
style="fill:#666;stroke:purple;stroke-width:0" />
</svg>

After

Width:  |  Height:  |  Size: 156 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

View File

@@ -0,0 +1 @@
.jv-container{box-sizing:border-box;position:relative}.jv-container.boxed{border:1px solid #eee;border-radius:6px}.jv-container.boxed:hover{box-shadow:0 2px 7px #00000026;border-color:transparent;position:relative}.jv-container.jv-light{background:#fff;white-space:nowrap;color:#525252;font-size:14px;font-family:Consolas,Menlo,Courier,monospace}.jv-container.jv-light .jv-ellipsis{color:#999;background-color:#eee;display:inline-block;line-height:.9;font-size:.9em;padding:0 4px 2px;margin:0 4px;border-radius:3px;vertical-align:2px;cursor:pointer;user-select:none}.jv-container.jv-light .jv-button{color:#49b3ff}.jv-container.jv-light .jv-key{color:#111;margin-right:4px}.jv-container.jv-light .jv-item.jv-array{color:#111}.jv-container.jv-light .jv-item.jv-boolean{color:#fc1e70}.jv-container.jv-light .jv-item.jv-function{color:#067bca}.jv-container.jv-light .jv-item.jv-number{color:#fc1e70}.jv-container.jv-light .jv-item.jv-object{color:#111}.jv-container.jv-light .jv-item.jv-undefined{color:#e08331}.jv-container.jv-light .jv-item.jv-string{color:#42b983;word-break:break-word;white-space:normal}.jv-container.jv-light .jv-item.jv-string .jv-link{color:#0366d6}.jv-container.jv-light .jv-code .jv-toggle:before{padding:0 2px;border-radius:2px}.jv-container.jv-light .jv-code .jv-toggle:hover:before{background:#eee}.jv-container .jv-code{overflow:hidden;padding:30px 20px}.jv-container .jv-code.boxed{max-height:300px}.jv-container .jv-code.open{max-height:initial!important;overflow:visible;overflow-x:auto;padding-bottom:45px}.jv-container .jv-toggle{background-image:url(/assets/icon.a080a8a4.svg);background-repeat:no-repeat;background-size:contain;background-position:center center;cursor:pointer;width:10px;height:10px;margin-right:2px;display:inline-block;transition:transform .1s}.jv-container .jv-toggle.open{transform:rotate(90deg)}.jv-container .jv-more{position:absolute;z-index:1;bottom:0;left:0;right:0;height:40px;width:100%;text-align:center;cursor:pointer}.jv-container .jv-more .jv-toggle{position:relative;top:40%;z-index:2;color:#888;transition:all .1s;transform:rotate(90deg)}.jv-container .jv-more .jv-toggle.open{transform:rotate(-90deg)}.jv-container .jv-more:after{content:"";width:100%;height:100%;position:absolute;bottom:0;left:0;z-index:1;background:linear-gradient(to bottom,rgba(0,0,0,0) 20%,rgba(230,230,230,.3) 100%);transition:all .1s}.jv-container .jv-more:hover .jv-toggle{top:50%;color:#111}.jv-container .jv-more:hover:after{background:linear-gradient(to bottom,rgba(0,0,0,0) 20%,rgba(230,230,230,.3) 100%)}.jv-container .jv-button{position:relative;cursor:pointer;display:inline-block;padding:5px;z-index:5}.jv-container .jv-button.copied{opacity:.4;cursor:default}.jv-container .jv-tooltip{position:absolute}.jv-container .jv-tooltip.right{right:15px}.jv-container .jv-tooltip.left{left:15px}.jv-container .j-icon{font-size:12px}.jv-node{position:relative}.jv-node:after{content:","}.jv-node:last-of-type:after{content:""}.jv-node.toggle{margin-left:13px!important}.jv-node .jv-node{margin-left:25px}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Pragma" content="no-cache">
<link rel="icon" href="{{BASE_URI}}/assets/title.8b763cb0.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>接口文档</title>
<script type="module" crossorigin src="{{BASE_URI}}/assets/index.4ac3587a.js"></script>
<link rel="modulepreload" href="{{BASE_URI}}/assets/vendor.08310185.js">
<link rel="stylesheet" href="{{BASE_URI}}/assets/vendor.0513d345.css">
<link rel="stylesheet" href="{{BASE_URI}}/assets/index.5b18e4ae.css">
</head>
<body>
<div id="app"></div>
</body>
</html>