用 Cloudflare Pages Functions 给博客音乐馆接入网易云会员音乐

背景

博客用 Hexo + 安知鱼主题搭建,部署在 Cloudflare Pages。安知鱼主题内置了音乐馆功能,左下角有一个悬浮播放器,还有一个独立的 /music/ 页面。音乐数据通过 MetingJS 从网易云拉取。

问题是:所有歌都只有 45 秒试听片段。

原因是 MetingJS 默认走的是第三方公共 API,匿名请求网易云,VIP / 版权歌曲只给试听。要听完整版,就得带上你自己的网易云会员 Cookie 去请求 API。

但 Cookie 是账号登录凭证,绝不能写进前端代码或 Git 仓库。

方案

在 Cloudflare Pages 项目根目录放一个 Pages Function,做一层”中间人”:

1
2
浏览器 → /api/meting (Pages Function) → 网易云 API
↑ Cookie 从环境变量读取,不暴露给前端
  • MetingJS 请求同源的 /api/meting,不跨域
  • Cookie 只存在 Cloudflare Pages 的加密机密里,代码和构建产物里都看不到
  • 音频 URL 自动从 HTTP 转 HTTPS,避免混合内容问题

实现步骤

1. 创建 Pages Function

在项目根目录新建 functions/api/meting.js:

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
33
34
35
36
37
const DEFAULT_API_BASE = "https://netease-cloud-music-api.fe-mm.com";
const DEFAULT_LEVEL = "exhigh";
const CACHE_SECONDS = 300;

function json(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: {
"content-type": "application/json; charset=utf-8",
"cache-control": `public, max-age=${CACHE_SECONDS}`,
},
});
}

function apiBase(env) {
return (env.NETEASE_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
}

async function requestNetease(env, path, params = {}) {
const url = new URL(`${apiBase(env)}${path}`);
const cookie = env.NETEASE_COOKIE;

Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== "") {
url.searchParams.set(key, String(value));
}
});

const headers = { "user-agent": "Mozilla/5.0" };
if (cookie) headers.cookie = cookie;

const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`网易云 API 请求失败:${response.status} ${path}`);
}
return response.json();
}

核心逻辑分三种请求类型:

  • type=playlist:先拿歌单详情,再批量请求歌曲详情和播放地址
  • type=song:单曲获取详情和播放地址
  • type=lyric:获取歌词文本

返回格式兼容 MetingJS 协议,每首歌包含 name、artist、url、cover、lrc 五个字段。

2. 修改主题配置

在 _config.anzhiyu.yml 中把 meting_api 指向同源接口:

1
2
3
CDN:
option:
meting_api: /api/meting?server=:server&type=:type&id=:id&r=:r

这样 MetingJS 就会请求你博客自己的 /api/meting,而不是第三方公共 API。

3. 配置歌单 ID

安知鱼主题有三个地方会引用歌单 ID,必须全部改掉:

文件 说明
_config.anzhiyu.yml 主题用户配置,nav_music.id
themes/anzhiyu/_config.yml 主题默认配置,会被合并
themes/anzhiyu/source/js/utils.js 前端 JS 里硬编码的 fallback
themes/anzhiyu/scripts/events/merge_config.js 构建时合并逻辑的默认值

踩坑提醒:只改 _config.anzhiyu.yml 是不够的。主题的 merge_config.js 在构建时会用默认值覆盖你的配置,utils.js 里还有个硬编码的 fallback。三处必须全改,否则构建产物里还是旧歌单。

4. 配置 Cloudflare Pages 环境变量

在 Cloudflare Pages 控制台 → 设置 → 变量和机密 中添加:

名称 类型 值
NETEASE_COOKIE 加密机密 MUSIC_U=你的Cookie值
NETEASE_API_BASE 普通变量 https://你的API地址
NETEASE_AUDIO_LEVEL 普通变量 exhigh

Cookie 格式是 MUSIC_U=xxxx,用等号不是冒号。Cookie 不写进代码,只存在 Cloudflare 加密机密里。

5. 部署

使用 Wrangler CLI 直接部署,不需要在 Cloudflare 控制台设置构建命令:

1
2
3
4
5
6
7
# 构建
npm run build

# 部署(会同时上传 public 静态文件和 functions 目录)
npx wrangler pages deploy public \
--project-name 你的项目名 \
--branch main

Wrangler 会自动识别项目根目录下的 functions/ 文件夹,把 Pages Functions 一起编译上传。

上游 API 的选择

Pages Function 需要通过 HTTPS 访问网易云 API。有几个选项:

  1. 公共 API:如 https://netease-cloud-music-api.fe-mm.com,开箱即用,但不受你控制
  2. 自建 API:在你的服务器上部署 NeteaseCloudMusicApi,配 HTTPS 域名和 443 端口,再用 Cloudflare DNS 代理

注意:Cloudflare Pages Functions 发出的 fetch 请求必须是 HTTPS。如果你的上游是裸 http://IP:3000,Cloudflare 会返回 522,Function 直接 502。

安全要点

  • Cookie 永远不进仓库:只通过 Cloudflare Pages 加密机密注入,代码里只有 env.NETEASE_COOKIE 的引用
  • Cookie 可选:Function 在没有配置 Cookie 时也能匿名工作,配置后自动带上会员凭证
  • 音频 URL 强制 HTTPS:网易云返回的音频地址有时是 HTTP,Function 会自动替换成 HTTPS,避免浏览器混合内容拦截
  • 缓存策略:响应带 5 分钟 cache-control,减少对上游的重复请求

最终效果

博客音乐馆加载你自己的网易云歌单,播放完整版歌曲,前端代码里看不到任何 Cookie。

部署命令一条就够:

1
2
npm run build && npx wrangler pages deploy public \
--project-name 你的项目名 --branch main