Bangumi 的部分域名(api.bgm.tv、next.bgm.tv、bangumi.tv)在当前网络环境下无法直接访问,通过 Cloudflare Worker 做反向代理来解决。
背景
Kazumi 项目依赖 Bangumi 开放 API 获取番剧元数据、评论、角色信息等。涉及三个上游域名:
| 域名 |
用途 |
api.bgm.tv |
基础 API(搜索、条目信息、剧集、角色) |
next.bgm.tv |
Next API(每日放送、趋势、评论、Staff) |
bangumi.tv |
官网资源(头像占位图、表情图片、页面链接) |
三个域名在内网环境均不可达,需要统一代理。
方案设计
使用一个 Cloudflare Worker,通过路径前缀区分上游:
1 2 3
| bangumi.mydomain.com/api/* → api.bgm.tv/* bangumi.mydomain.com/next/* → next.bgm.tv/* bangumi.mydomain.com/bgm/* → bangumi.tv/*
|
为什么用路径前缀而不是子域名
Cloudflare Worker 免费版每个 worker 绑定一个路由,用路径前缀可以在单个 worker 内处理多个上游,无需额外配置多条 DNS 记录和路由规则。
Worker 实现
核心逻辑:
- 路由解析 — 根据 pathname 的前缀匹配上游域名
- URL 重写 — 去掉路径前缀,拼接上游地址和 query string
- Header 过滤 — 移除 Cloudflare 注入的头(
cf-connecting-ip 等),设置正确的 Host
- CORS 支持 — 所有响应附加跨域头,前端/移动端可直接调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const UPSTREAM_MAP = { '/api': 'https://api.bgm.tv', '/next': 'https://next.bgm.tv', '/bgm': 'https://bangumi.tv', };
const DEFAULT_UPSTREAM = 'https://api.bgm.tv';
const FORBIDDEN_HEADERS = [ 'host', 'cf-connecting-ip', 'cf-connecting-ipv6', 'cf-ipcountry', 'cf-ray', 'cf-visitor', 'cf-worker', 'cf-cache-status', 'x-real-ip', 'x-forwarded-for', 'x-forwarded-proto', ];
function resolveUpstream(pathname) { for (const [prefix, origin] of Object.entries(UPSTREAM_MAP)) { if (pathname === prefix || pathname.startsWith(prefix + '/')) { return { origin, prefix }; } } return { origin: DEFAULT_UPSTREAM, prefix: '' }; }
function buildTargetURL(request) { const url = new URL(request.url); const { origin, prefix } = resolveUpstream(url.pathname); const target = new URL( url.pathname.substring(prefix.length) + url.search, origin ); target.protocol = 'https:'; return { targetURL: target, upstreamHost: new URL(origin).host }; }
|
处理请求时过滤敏感头并附加 CORS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| async function handleRequest(event) { const request = event.request; const url = new URL(request.url);
if (request.method === 'OPTIONS') { return handleOPTIONS(); }
const { targetURL, upstreamHost } = buildTargetURL(request); const filteredHeaders = filterRequestHeaders(request.headers, upstreamHost);
const originRequest = new Request(targetURL.toString(), { method: request.method, headers: filteredHeaders, body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null, redirect: 'follow', });
const response = await fetch(originRequest); const newHeaders = withCORSHeaders(response.headers);
return new Response(response.body, { status: response.status, statusText: response.statusText, headers: newHeaders, }); }
|
客户端适配
Kazumi 项目中所有 Bangumi 相关的 URL 统一改为走代理:
1 2 3 4 5 6 7 8 9
|
static const String bangumiIndex = 'https://bangumi.mydomain.com/bgm/';
static const String bangumiAPIDomain = 'https://bangumi.mydomain.com/api/';
static const String bangumiAPINextDomain = 'https://bangumi.mydomain.com/next';
|
图片资源等硬编码 URL 也需要同步替换:
1 2 3 4 5
| NetworkImage('https://bangumi.tv/img/info_only.png')
NetworkImage('https://bangumi.mydomain.com/bgm/img/info_only.png')
|
部署
- 在 Cloudflare Dashboard → Workers 中创建 Worker
- 粘贴脚本,保存并部署
- 绑定自定义域名(如
bangumi.mydomain.com)
注意事项
bangumi.tv 的资源(图片、页面)通过 /bgm/ 前缀代理,路径映射要确保去掉前缀后与原始路径一致
- Worker 设置
redirect: 'follow',上游如有重定向会自动跟随
- 如果还有其他 Bangumi CDN 域名不可达(如
lain.bgm.tv),可以按相同模式加 /lain 前缀