Files
MusicServer/test/bili-cdn-test.ts
snowy-yang cec63f864e 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 分级选址)」软断言项。
2026-10-04 17:29:28 +08:00

58 lines
2.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)