refactor(routes)!: 移除旧版 query 风格路由
This commit is contained in:
1 parent
10de5582c7
commit
c1c8aa4b94
5 files changed
+55
-185
No files matched your search
+12
-122
@@ -124,13 +124,11 @@ export const musicRoutes = async(fastify) => {
|
||||
}
|
||||
})
|
||||
|
||||
// ---------- 歌曲资源(路径参数风格 + 旧 query 风格别名) ----------
|
||||
// ---------- 歌曲资源(路径参数风格) ----------
|
||||
|
||||
const urlHandler = async(request) => {
|
||||
const q = request.params.id
|
||||
? { source: request.params.source, id: request.params.id, ...request.query }
|
||||
: request.query
|
||||
const { source, id, quality, albumId } = q
|
||||
const { source, id } = request.params
|
||||
const { quality, albumId } = request.query
|
||||
const m = getSource(musicSdk, source, 'getMusicUrl')
|
||||
const songInfo = { songmid: id, hash: id, copyrightId: id, albumId: albumId || '', _types: {} }
|
||||
let result
|
||||
@@ -161,26 +159,9 @@ export const musicRoutes = async(fastify) => {
|
||||
},
|
||||
}, urlHandler)
|
||||
|
||||
fastify.get('/url', {
|
||||
schema: {
|
||||
summary: '获取播放地址(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['歌曲资源'],
|
||||
querystring: qs({
|
||||
source: pSource('url'),
|
||||
id: pId,
|
||||
quality: pQuality,
|
||||
albumId: { type: 'string', description: '专辑 id' },
|
||||
}, ['source', 'id']),
|
||||
response: responses('MusicUrlResult'),
|
||||
},
|
||||
}, urlHandler)
|
||||
|
||||
const lyricHandler = async(request) => {
|
||||
const q = request.params.id
|
||||
? { source: request.params.source, id: request.params.id, ...request.query }
|
||||
: request.query
|
||||
const { source, id, name, interval } = q
|
||||
const { source, id } = request.params
|
||||
const { name, interval } = request.query
|
||||
const m = getSource(musicSdk, source, 'getLyric')
|
||||
let result
|
||||
try {
|
||||
@@ -206,26 +187,9 @@ export const musicRoutes = async(fastify) => {
|
||||
},
|
||||
}, lyricHandler)
|
||||
|
||||
fastify.get('/lyric', {
|
||||
schema: {
|
||||
summary: '获取歌词(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['歌曲资源'],
|
||||
querystring: qs({
|
||||
source: pSource('lyric'),
|
||||
id: pId,
|
||||
name: { type: 'string', description: '歌名' },
|
||||
interval: { type: 'string', description: '时长(mm:ss)' },
|
||||
}, ['source', 'id']),
|
||||
response: responses('LyricInfo'),
|
||||
},
|
||||
}, lyricHandler)
|
||||
|
||||
const picHandler = async(request) => {
|
||||
const q = request.params.id
|
||||
? { source: request.params.source, id: request.params.id, ...request.query }
|
||||
: request.query
|
||||
const { source, id, albumId } = q
|
||||
const { source, id } = request.params
|
||||
const { albumId } = request.query
|
||||
const m = getSource(musicSdk, source, 'getPic')
|
||||
let result
|
||||
try {
|
||||
@@ -248,20 +212,6 @@ export const musicRoutes = async(fastify) => {
|
||||
},
|
||||
}, picHandler)
|
||||
|
||||
fastify.get('/pic', {
|
||||
schema: {
|
||||
summary: '获取封面(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['歌曲资源'],
|
||||
querystring: qs({
|
||||
source: pSource('pic'),
|
||||
id: pId,
|
||||
albumId: { type: 'string', description: '专辑 id' },
|
||||
}, ['source', 'id']),
|
||||
response: responses('PicResult'),
|
||||
},
|
||||
}, picHandler)
|
||||
|
||||
// ---------- 榜单 ----------
|
||||
|
||||
fastify.get('/boards/:source', {
|
||||
@@ -276,19 +226,6 @@ export const musicRoutes = async(fastify) => {
|
||||
return m.leaderboard.getBoards().catch(err => { throw upstream(err.message) })
|
||||
})
|
||||
|
||||
fastify.get('/boards', {
|
||||
schema: {
|
||||
summary: '获取榜单列表(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['榜单'],
|
||||
querystring: qs({ source: pSource('boards') }, ['source']),
|
||||
response: responses('BoardListResult'),
|
||||
},
|
||||
}, async(request) => {
|
||||
const m = getSource(musicSdk, request.query.source, 'leaderboard')
|
||||
return m.leaderboard.getBoards().catch(err => { throw upstream(err.message) })
|
||||
})
|
||||
|
||||
const boardHandler = (source, id, page) => {
|
||||
const m = getSource(musicSdk, source, 'leaderboard')
|
||||
// 榜单列表返回的 id 带平台前缀(如 kw__93),详情接口需要裸 bangid(93),这里做归一化
|
||||
@@ -311,22 +248,6 @@ export const musicRoutes = async(fastify) => {
|
||||
request.params.source, request.params.boardId, request.query.page,
|
||||
))
|
||||
|
||||
fastify.get('/board', {
|
||||
schema: {
|
||||
summary: '获取榜单详情(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['榜单'],
|
||||
querystring: qs({
|
||||
source: pSource('boards'),
|
||||
id: { type: 'string', description: '榜单 id' },
|
||||
page: pPage,
|
||||
}, ['source', 'id']),
|
||||
response: responses('BoardDetailResult'),
|
||||
},
|
||||
}, async(request) => boardHandler(
|
||||
request.query.source, request.query.id, request.query.page,
|
||||
))
|
||||
|
||||
// ---------- 歌单 & 热搜 ----------
|
||||
|
||||
fastify.get('/hot-search/:source', {
|
||||
@@ -341,19 +262,6 @@ export const musicRoutes = async(fastify) => {
|
||||
return m.hotSearch.getList().catch(err => { throw upstream(err.message) })
|
||||
})
|
||||
|
||||
fastify.get('/hotSearch', {
|
||||
schema: {
|
||||
summary: '获取热搜词(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['歌单'],
|
||||
querystring: qs({ source: pSource('hotSearch') }, ['source']),
|
||||
response: responses('HotSearchResult'),
|
||||
},
|
||||
}, async(request) => {
|
||||
const m = getSource(musicSdk, request.query.source, 'hotSearch')
|
||||
return m.hotSearch.getList().catch(err => { throw upstream(err.message) })
|
||||
})
|
||||
|
||||
const songListHandler = (source, page, sortId, tagId) => {
|
||||
const m = getSource(musicSdk, source, 'songList')
|
||||
// sortId 各平台词汇不同(kw 'new'、tx 5、wy 'hot'…),省略时用平台自己声明的默认排序
|
||||
@@ -365,34 +273,16 @@ export const musicRoutes = async(fastify) => {
|
||||
summary: '获取歌单列表',
|
||||
tags: ['歌单'],
|
||||
params: params({ source: pSource('songList') }),
|
||||
querystring: qs({
|
||||
page: pPage,
|
||||
sortId: { type: ['integer', 'string'], description: '排序 id(平台相关,见 /sources 对应平台文档;省略时用平台默认)' },
|
||||
tagId: { type: ['integer', 'string'], description: '标签 id(平台相关,kw 为 `173-digest` 形态字符串;0 为全部)' },
|
||||
}),
|
||||
response: responses('SongListResult'),
|
||||
},
|
||||
}, async(request) => {
|
||||
const { page, sortId, tagId } = request.query
|
||||
return songListHandler(request.params.source, page, sortId, tagId)
|
||||
})
|
||||
|
||||
fastify.get('/songList', {
|
||||
schema: {
|
||||
summary: '获取歌单列表(旧版 query 风格)',
|
||||
deprecated: true,
|
||||
tags: ['歌单'],
|
||||
querystring: qs({
|
||||
source: pSource('songList'),
|
||||
page: pPage,
|
||||
sortId: { type: ['integer', 'string'], description: '排序 id(平台相关,省略时用平台默认)' },
|
||||
tagId: { type: ['integer', 'string'], description: '标签 id(平台相关,kw 为 `173-digest` 形态字符串)' },
|
||||
}, ['source']),
|
||||
sortId: { type: ['integer', 'string'], description: '排序 id(平台相关,见 /sources 对应平台文档;省略时用平台默认)' },
|
||||
tagId: { type: ['integer', 'string'], description: '标签 id(平台相关,kw 为 `173-digest` 形态字符串;0 为全部)' },
|
||||
}),
|
||||
response: responses('SongListResult'),
|
||||
},
|
||||
}, async(request) => {
|
||||
const { source, page, sortId, tagId } = request.query
|
||||
return songListHandler(source, page, sortId, tagId)
|
||||
const { page, sortId, tagId } = request.query
|
||||
return songListHandler(request.params.source, page, sortId, tagId)
|
||||
})
|
||||
|
||||
// ---------- 系统 ----------
|
||||
|
||||
+9
-10
@@ -1,16 +1,15 @@
|
||||
// HTTP 服务器入口(Fastify)—— 多平台音乐聚合 API
|
||||
// 设计目标:标准 HTTP 语义 + FastAPI 风格的自动文档(校验与文档均由 JSON Schema 驱动)
|
||||
//
|
||||
// 规范路由(路径参数风格) 旧版路由(query 风格,保留兼容)
|
||||
// GET /search (同左)
|
||||
// GET /find (同左)
|
||||
// GET /url/:source/:id GET /url?source=&id=
|
||||
// GET /lyric/:source/:id GET /lyric?source=&id=
|
||||
// GET /pic/:source/:id GET /pic?source=&id=
|
||||
// GET /boards/:source GET /boards?source=
|
||||
// GET /boards/:source/:boardId GET /board?source=&id=
|
||||
// GET /hot-search/:source GET /hotSearch?source=
|
||||
// GET /song-list/:source GET /songList?source=
|
||||
// GET /search?name=&singer=&source=&page=&limit= 搜索(source 省略时聚合全部平台)
|
||||
// GET /find?name=&singer=&albumName=&interval= 跨平台精确匹配
|
||||
// GET /url/:source/:id 播放地址
|
||||
// GET /lyric/:source/:id 歌词
|
||||
// GET /pic/:source/:id 封面
|
||||
// GET /boards/:source 榜单列表
|
||||
// GET /boards/:source/:boardId 榜单详情
|
||||
// GET /hot-search/:source 热搜词
|
||||
// GET /song-list/:source 歌单列表
|
||||
// GET /sources /health GET /docs /redoc /openapi.json
|
||||
//
|
||||
// 状态码语义:200 成功;422 参数校验失败;404 路由或平台不存在;405 方法不允许;502 上游平台失败;500 内部错误
|
||||
|
||||
+19
-38
@@ -2,7 +2,8 @@
|
||||
// 运行:node src/smoke-test.js
|
||||
//
|
||||
// 覆盖:搜索(各平台 + 聚合)、URL(kw/wy/tx)、歌词、封面、榜单详情、热搜、歌单、
|
||||
// 跨平台匹配 /find、路径参数风格路由、标准 HTTP 语义(404/405/422/302)、文档页
|
||||
// 跨平台匹配 /find、路径参数风格路由、标准 HTTP 语义(404/405/422/302)、
|
||||
// 文档页、旧 query 路由移除回归
|
||||
|
||||
import http from 'node:http'
|
||||
import { spawn } from 'node:child_process'
|
||||
@@ -124,23 +125,23 @@ await test('聚合搜索', async() => {
|
||||
return `${r.data.list.length} 条结果`
|
||||
})
|
||||
|
||||
// URL(用已知歌曲 id)
|
||||
// URL(用已知歌曲 id,路径参数风格)
|
||||
await test('URL kw 320k', async() => {
|
||||
const r = await get('/url?source=kw&id=28423286&quality=320k')
|
||||
const r = await get('/url/kw/28423286?quality=320k')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.url) throw new Error('无 url')
|
||||
return r.data.url.slice(0, 60)
|
||||
})
|
||||
|
||||
await test('URL wy 128k', async() => {
|
||||
const r = await get('/url?source=wy&id=3357698666&quality=128k')
|
||||
const r = await get('/url/wy/3357698666?quality=128k')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.url) throw new Error('无 url')
|
||||
return r.data.url.slice(0, 60)
|
||||
})
|
||||
|
||||
await test('URL tx 320k', async() => {
|
||||
const r = await get('/url?source=tx&id=003C9fBv2K4x8b&quality=320k')
|
||||
const r = await get('/url/tx/003C9fBv2K4x8b?quality=320k')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.url) throw new Error('无 url')
|
||||
return r.data.url.slice(0, 60)
|
||||
@@ -148,7 +149,7 @@ await test('URL tx 320k', async() => {
|
||||
|
||||
// 歌词
|
||||
await test('歌词 kw', async() => {
|
||||
const r = await get('/lyric?source=kw&id=28423286')
|
||||
const r = await get('/lyric/kw/28423286')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.lyric) throw new Error('无歌词')
|
||||
return `${r.data.lyric.slice(0, 30)}...`
|
||||
@@ -156,7 +157,7 @@ await test('歌词 kw', async() => {
|
||||
|
||||
// 封面
|
||||
await test('封面 kw', async() => {
|
||||
const r = await get('/pic?source=kw&id=28423286')
|
||||
const r = await get('/pic/kw/28423286')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.url) throw new Error('无 url')
|
||||
return r.data.url.slice(0, 60)
|
||||
@@ -165,7 +166,7 @@ await test('封面 kw', async() => {
|
||||
// 榜单 & 热搜
|
||||
for (const source of ['kw', 'kg', 'tx', 'wy', 'mg']) {
|
||||
await test(`榜单 ${source}`, async() => {
|
||||
const r = await get(`/boards?source=${source}`)
|
||||
const r = await get(`/boards/${source}`)
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.list?.length) throw new Error('空列表')
|
||||
return `${r.data.list.length} 个榜单`
|
||||
@@ -173,13 +174,13 @@ for (const source of ['kw', 'kg', 'tx', 'wy', 'mg']) {
|
||||
}
|
||||
|
||||
await test('热搜 kw', async() => {
|
||||
const r = await get('/hotSearch?source=kw')
|
||||
const r = await get('/hot-search/kw')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.list?.length) throw new Error('空列表')
|
||||
return r.data.list[0]
|
||||
})
|
||||
|
||||
// ---- 标准 HTTP 语义与文档(新版路由) ----
|
||||
// ---- 标准 HTTP 语义与文档 ----
|
||||
|
||||
await test('平台列表 /sources', async() => {
|
||||
const r = await get('/sources')
|
||||
@@ -190,34 +191,6 @@ await test('平台列表 /sources', async() => {
|
||||
return `${r.data.platforms.length} 个平台`
|
||||
})
|
||||
|
||||
await test('URL 路径风格 /url/kw/:id', async() => {
|
||||
const r = await get('/url/kw/28423286?quality=320k')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.url) throw new Error('无 url')
|
||||
return r.data.url.slice(0, 60)
|
||||
})
|
||||
|
||||
await test('歌词 路径风格 /lyric/kw/:id', async() => {
|
||||
const r = await get('/lyric/kw/28423286')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.lyric) throw new Error('无歌词')
|
||||
return `${r.data.lyric.slice(0, 30)}...`
|
||||
})
|
||||
|
||||
await test('榜单 路径风格 /boards/:source', async() => {
|
||||
const r = await get('/boards/kg')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.list?.length) throw new Error('空列表')
|
||||
return `${r.data.list.length} 个榜单`
|
||||
})
|
||||
|
||||
await test('封面 路径风格 /pic/kw/:id', async() => {
|
||||
const r = await get('/pic/kw/28423286')
|
||||
if (r.code != 0) throw new Error(r.msg)
|
||||
if (!r.data?.url) throw new Error('无 url')
|
||||
return r.data.url.slice(0, 60)
|
||||
})
|
||||
|
||||
await test('榜单详情 路径风格 /boards/:source/:boardId', async() => {
|
||||
// 用列表返回的带前缀 id(回归:详情接口应自动归一化为裸 bangid)
|
||||
const r = await get('/boards/kw/kw__93?page=1')
|
||||
@@ -267,6 +240,14 @@ await test('404 未知接口', async() => {
|
||||
return JSON.parse(r.body).msg
|
||||
})
|
||||
|
||||
await test('旧 query 路由已移除(404)', async() => {
|
||||
const r = await getRaw('/url?source=kw&id=28423286')
|
||||
if (r.status !== 404) throw new Error(`期望 404,实际 ${r.status}`)
|
||||
const body = JSON.parse(r.body)
|
||||
if (!/接口不存在/.test(body.msg)) throw new Error(`响应异常: ${body.msg}`)
|
||||
return body.msg
|
||||
})
|
||||
|
||||
await test('405 方法不允许(静态路由)', async() => {
|
||||
const r = await postRaw('/search')
|
||||
if (r.status !== 405) throw new Error(`期望 405,实际 ${r.status}`)
|
||||
|
||||
Reference in new issue
Block a user