fix(docs): 豁免 /docs/json 信封包裹,修复 Swagger UI 无法加载规范

This commit is contained in:
snowy-yang committed 2026-09-16 20:52:38 +08:00
1 parent ab90c5b4c4
commit 10de5582c7
2 files changed
+17 -5

No files matched your search

+8 -5
View File
@@ -31,6 +31,7 @@ import { redocHtml } from './openapi/ui.js'
// 默认端口 16688:本机 TCP 动态端口范围是 1024~15000(出站连接会随机占用低端端口),避开该区间 // 默认端口 16688:本机 TCP 动态端口范围是 1024~15000(出站连接会随机占用低端端口),避开该区间
const PORT = process.env.PORT || 16688 const PORT = process.env.PORT || 16688
const DOCS_PREFIX = '/docs'
const API_TITLE = 'MusicServer 聚合音乐 API' const API_TITLE = 'MusicServer 聚合音乐 API'
const API_DESCRIPTION = '多平台音乐聚合接口:搜索、播放地址、歌词、封面、榜单、歌单。' + const API_DESCRIPTION = '多平台音乐聚合接口:搜索、播放地址、歌词、封面、榜单、歌单。' +
'支持 kw(酷我)/ kg(酷狗)/ tx(QQ音乐)/ wy(网易云)/ mg(咪咕)平台。' '支持 kw(酷我)/ kg(酷狗)/ tx(QQ音乐)/ wy(网易云)/ mg(咪咕)平台。'
@@ -74,10 +75,13 @@ fastify.addHook('onResponse', async(request, reply) => {
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url} → ${reply.statusCode} (${Math.round(reply.elapsedTime)}ms)`) console.log(`[${new Date().toISOString()}] ${request.method} ${request.url} → ${reply.statusCode} (${Math.round(reply.elapsedTime)}ms)`)
}) })
// 成功响应统一包成 { code: 0, data };文档页/raw 路由与非 200 响应不包 // 成功响应统一包成 { code: 0, data };文档页/raw 路由与非 200 响应不包。
// @fastify/swagger-ui 的规范端点 {DOCS_PREFIX}/json 由插件内部注册、无法加 raw 标记,
// 必须豁免:Swagger UI 强制从该地址加载规范,包了信封顶层就没有 openapi 字段,页面直接报错
fastify.addHook('preSerialization', async(request, reply, payload) => { fastify.addHook('preSerialization', async(request, reply, payload) => {
if (reply.statusCode !== 200) return payload if (reply.statusCode !== 200) return payload
if (request.routeOptions.config?.raw) return payload if (request.routeOptions.config?.raw) return payload
if (request.routeOptions.url === `${DOCS_PREFIX}/json`) return payload
if (payload === null || typeof payload === 'object') return { code: 0, data: payload } if (payload === null || typeof payload === 'object') return { code: 0, data: payload }
return payload return payload
}) })
@@ -141,10 +145,9 @@ fastify.register(swagger, {
}, },
}) })
fastify.register(swaggerUi, { // 注意:插件初始化器会把规范地址强制为 {DOCS_PREFIX}/json(uiConfig 的 url 会被覆盖),
routePrefix: '/docs', // 真正对外文档化的规范地址是下方带 raw 标记的 /openapi.json
url: '/openapi.json', fastify.register(swaggerUi, { routePrefix: DOCS_PREFIX })
})
fastify.get('/openapi.json', { fastify.get('/openapi.json', {
config: { raw: true }, config: { raw: true },
+9
View File
@@ -305,6 +305,15 @@ await test('OpenAPI 规范 /openapi.json', async() => {
return `${Object.keys(spec.paths).length} 个路径` return `${Object.keys(spec.paths).length} 个路径`
}) })
await test('Swagger UI 规范端点 /docs/json(不可包信封)', async() => {
const r = await getRaw('/docs/json')
if (r.status !== 200) throw new Error(`期望 200,实际 ${r.status}`)
const spec = JSON.parse(r.body)
// Swagger UI 强制从该地址加载规范:若被全局信封钩子包成 { code, data },顶层无 openapi 字段,文档页直接报错
if (!spec.openapi?.startsWith('3.')) throw new Error(`顶层缺少 openapi 字段(疑似被信封包裹): ${r.body.slice(0, 80)}`)
return spec.openapi
})
// 汇总 // 汇总
const passed = results.filter(r => r.ok).length const passed = results.filter(r => r.ok).length
const failed = results.filter(r => !r.ok) const failed = results.filter(r => !r.ok)