fix(bili): 取流 CDN 分级选址——backupUrl 按 host 分级探测,根治 PCDN 点歌卡死

playurl 的 baseUrl 大概率落在 PCDN(京东云无线宝 mcdn.bilivideo.cn、迅雷
mountaintoys 等),旧代码只取 baseUrl(backupUrl 0 处使用)且探测失败仍
原样返回坏地址,点歌端连 PCDN 挂死。现候选 = baseUrl+backupUrl 按 host
分级(upos/Akamai 正规 CDN > cn-* 等 bilivideo.com 系 > PCDN),探测改为
fetch 只读响应头即断开(不等 body,CDN 忽略 Range 回 200 全量时不再整文件
缓冲),单跳 8s / 总预算 20s / 至多 3 候选,全败回退最优候选(fail-soft);
CDN 探测不再携带 Cookie(直链自带签名,避免登录态泄给第三方 PCDN 域名)。
getAudioUrl 返回 urls[],parse 改取 urls[0]。

新增 test/bili-cdn-test.ts(SDK 层 PCDN 硬断言,并入 pnpm test)与 smoke
「URL bili(CDN 分级选址)」软断言项。
This commit is contained in:
snowy-yang committed 2026-10-04 17:29:28 +08:00
1 parent e1357dd412
commit cec63f864e
5 files changed
+186 -41

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
// bili 取流 CDN 分级选址回归 —— SDK 层直调(不依赖服务器实例,含真实 B 站上游请求)
// 运行:node test/bili-cdn-test.ts(已并入 pnpm test)
//
// 覆盖:getMusicUrl 返回地址不落在 PCDN(mcdn 无线宝/mountaintoys 迅雷/szbdyd/nexusedgeio),
// parse 路径填充的 url 同样校验。
// 判定:命中 PCDN 即失败——fail-soft 兜底意味着 upos/cn 候选 20s 探测全败,是真实的选址退化信号
// (smoke 的「URL bili」项走 HTTP 层、只软警告;本文件是 SDK 层的硬断言)。
import biliApi from '../sdk/apis/bili.ts'
import { parseSong } from '../sdk/bili/parse.ts'
/** 与 sdk/apis/bili.ts 的 hostRank 同构:0=正规 CDN 1=B站系 2=PCDN */
const hostRank = (url: string): number => {
let host = ''
try { host = new URL(url).hostname.toLowerCase() } catch { return 2 }
if (host.endsWith('.akamaized.net')) return 0
if (host.endsWith('.bilivideo.com')) return host.startsWith('upos-') ? 0 : 1
return 2
}
const LABEL = ['正规CDN', 'B站系', 'PCDN(坏)']
const results: { name: string; ok: boolean; detail?: any }[] = []
const test = async(name: string, fn: () => Promise<any>) => {
try {
const result = await fn()
results.push({ name, ok: true, detail: result })
console.log(`✅ ${name}${result ? `: ${result}` : ''}`)
} catch (err: any) {
results.push({ name, ok: false, detail: err.message })
console.log(`❌ ${name}: ${err.message}`)
}
}
// 固定样本(2026-10-04 实测均有多分P候选与 upos 备源);换样本时优先选多分P、热度中等的稿件
const CASES = ['BV1BZbSzZEGT', 'BV1GJ411x7h7', 'BV1d4411N7zD', 'BV1EC41147U1', 'BV19F5k6VEEp']
for (const bvid of CASES) {
await test(`取流 ${bvid} 落正规 CDN`, async() => {
const { promise } = biliApi.getMusicUrl({ songmid: bvid }, '128k')
const res = await promise
const rank = hostRank(res.url)
if (rank === 2) throw new Error(`命中 PCDN:${new URL(res.url).host}(backupUrl 探测疑似全败)`)
return `${LABEL[rank]} ${new URL(res.url).host}`
})
}
await test('parse 填充 url 落正规 CDN', async() => {
const song = await parseSong('https://www.bilibili.com/video/BV1GJ411x7h7')
if (!song.url) throw new Error('parse 未填充 url')
const rank = hostRank(String(song.url))
if (rank === 2) throw new Error(`命中 PCDN:${new URL(String(song.url)).host}`)
return `${LABEL[rank]} ${new URL(String(song.url)).host}`
})
const failed = results.filter(r => !r.ok).length
console.log(`\n===== 结果: ${results.length - failed}/${results.length} 通过 =====`)
process.exit(failed ? 1 : 0)