自建 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 指定名称,避免和已存在的同名项目撞车):
cd makers-proxyedgeone 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 就能少一次往返。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!











