暂未播放
0:00
0:00

自建 Meting API 并让国内访问不再慢:Vercel 香港节点 + 歌词代理 + 边缘缓存

1218 字
6 分钟
自建 Meting API 并让国内访问不再慢:Vercel 香港节点 + 歌词代理 + 边缘缓存

为什么不直接用现成的 API#

主题 Meting 默认指向的公共 API 用不了了,找了一圈也没几个能长期用的公共实例。于是干脆把 Meting-UI-API 这个 fork 自己部署了一份,跑在 Vercel 上。

本以为克隆个仓库、设两个环境变量就完事,上线后才发现两个绕不开的问题:国内访问慢,以及 QQ 音乐的歌词拿不到。这篇记录怎么填这两个坑,以及哪几步才是真正起作用的。

慢的原因:Vercel 函数区域#

Vercel 函数默认部署在美国东部(IAD1),这才是国内访问慢的原因。

点一首歌,播放器实际会向 API 发 4 次请求:song → url → pic → lrc。我在中国,Vercel 在美国。每一次请求都是一次跨境往返,叠加起来就是 4 倍 RTT。

把函数区域从美东改到**香港(hkg1)**后,体感是立竿见影的——链路短了一大截,那 4 次往返的叠加延迟直接砍掉。这一步比任何代码层面的优化都直接。

最好的做法是直接写死在 vercel.json,免得哪天换机器、重新部署后忘了在控制台设:

{
"env": { "RUNTIME": "vercel" },
"functions": {
"api/index.js": { "regions": ["hkg1"] }
},
"rewrites": [
{ "source": "/(.*)", "destination": "/api" }
]
}

控制台也能改(Settings → Functions → Region → Hong Kong),但写进配置文件才不会因为忘记设置又回到美东。改完记得 Redeploy。

QQ 歌词拿不到:必须用「固定区域」的代理#

现象:在 Vercel 上,type=lrc&server=tencent 返回 {"error":"no data"},但 search/url/pic 都正常,netease 的歌词也正常。

原因:QQ 音乐的歌词接口 c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg 对境外 IP 有限制,其余接口不校验。所以只需要在请求 tencent 歌词时,把域名换成国内的中转服务即可。

我在 api.js 里留了一个开关:

const LYRIC_PROXY = process.env.LYRIC_PROXY || '';
// ...
if (type === 'lrc' && server === 'tencent' && LYRIC_PROXY) {
const proxyBase = LYRIC_PROXY.replace(/\/$/, '');
const origCurl = meting._curl.bind(meting);
meting._curl = (url, body) =>
origCurl(String(url).replace(/^https:\/\/c\.y\.qq\.com/, proxyBase), body);
}

只有 tencent 歌词会走代理,其他请求完全不受影响。留空则走直连(国内/无限制环境正常工作)。

不要用「边缘函数」做这个代理#

我一开始用的是 EdgeOne 的边缘函数,结果在 Vercel 上依然拿不到歌词。排查了半天才突然想到:

⚠️ 边缘函数是就近执行——谁调用它,它就跑在谁附近的边缘节点上。

Vercel 在美国 → 代理函数跑在美国边缘 → 从美国 IP 去抓 c.y.qq.com → 还是被 QQ 墙。等于白忙活。

正解是用 EdgeOne Makers(Cloud Functions),它是「固定区域」执行,能在 edgeone.json 里把地域钉死:

{
"mainlandRegions": ["ap-guangzhou"],
"overseasRegions": ["ap-guangzhou"],
"cloudFunctions": { "maxDuration": 30 }
}

广州节点(ap-guangzhou)永远是国内 IP,从国内抓 QQ 歌词 → 放行。代理函数本身极简,就是个带白名单的 fetch 转发:

const UPSTREAM = 'https://c.y.qq.com';
export async function onRequest(context) {
const u = new URL(context.request.url);
if (!u.pathname.startsWith('/lyric/')) {
return new Response('forbidden', { status: 403 });
}
const target = UPSTREAM + u.pathname + u.search;
const headers = new Headers();
headers.set('host', 'c.y.qq.com');
headers.set('referer', 'https://y.qq.com/');
headers.set('user-agent', 'Mozilla/5.0 ... Chrome/120 Safari/537.36');
const upstream = await fetch(target, { method: context.request.method, headers });
const text = await upstream.text();
return new Response(text, { status: upstream.status });
}

部署命令(注意 -n 指定名称,避免和已存在的同名项目撞车):

Terminal window
cd makers-proxy
edgeone makers deploy -n meting-qq-proxy

拿到 Makers 默认域名后,把它填进 Vercel 的 LYRIC_PROXY 环境变量,重新 Deploy 即可。

同理,腾讯云 SCF、阿里云 FC、你自己的国内 VPS,只要「执行位置固定在国内」都能干这活,思路完全一致。

锦上添花:边缘缓存 + 内联直链#

区域改香港之后,单次请求已经很快了。这两项优化是进一步把体验做稳,属于「有了更好」。

边缘缓存:之前所有响应都没设缓存头,Vercel 不会边缘缓存,重复请求每次都回源。加上 s-maxage 后,重复请求由 Vercel 边缘就近返回:

const setEdgeCache = (c, type) => {
const ttl = (type === 'url' || type === 'pic') ? 600 : 3600;
c.header('Cache-Control', `public, s-maxage=${ttl}, max-age=60`);
};

错误响应不缓存,避免把「无数据」也顶到边缘上。

内联播放直链(&fill=1:默认情况下,单曲接口返回的 url 是一个「再去拉一次」的 API 地址,播放器拿到后还要再发一次 type=url 请求才拿到真正的 mp3。等于白送一次往返。加上 fill=1 后,服务端并行把直链解析好直接塞进 url 字段:

const audioUrl = urlId
? (fillSong ? await resolveAudioUrl(server, urlId, cookie)
: `${get_url(c)}?server=${server}&type=url&id=${urlId}`)
: '';

这里有个取舍:只对 type=song(单曲)做内联,playlist/search/artist 这些返回数组的不内联——否则一张歌单几十首,服务端要并发去打几十次接口,反而把自己打挂。所以内联保持「按需开启」(默认关),播放器调 type=song 时拼上 &fill=1 就能少一次往返。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
自建 Meting API 并让国内访问不再慢:Vercel 香港节点 + 歌词代理 + 边缘缓存
https://x1anyu.cn/posts/10/
作者
羡鱼
发布于
2026-08-27
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
羡鱼
临渊空慕水中鱼, 不如携风自渡河.
分类
标签
最新动态
站点统计
文章
14
分类
7
标签
35
总字数
19,969
运行时长
0
最后活动
0 天前
文章目录