主题
NeteaseCloudMusicApi 部署指南
关于本指南
本指南详细介绍如何部署 NeteaseCloudMusicApi 服务,包括多种部署方式、安全代理实现、登录方法和安全配置。特别强调了安全性,防止 API 被滥用。
概述
NeteaseCloudMusicApi 是一个基于 Node.js 的网易云音乐 API 服务,通过跨站请求伪造 (CSRF) 和伪造请求头的方式调用官方 API。
重要提示:原项目 Binaryify/NeteaseCloudMusicApi 已于 2024 年 4 月 16 日归档并停止维护。建议使用社区维护的活跃分支,如 NeteaseCloudMusicApiEnhanced 或 Binaryify/NeteaseCloudMusicApi 的 GitLab 镜像。
本指南将详细介绍如何部署该 API 服务,并提供安全的代理服务器实现,支持多种登录方式和严格的安全配置。
环境要求
系统要求
请确保您的系统满足以下最低要求:
- Node.js: 16+ 版本(推荐 18+)
- npm: 8+ 版本
- 操作系统: Windows、macOS、Linux(强烈建议使用中国大陆服务器)
快速部署
使用 git clone
1. 安装
shell
# 使用社区维护版本(推荐)
$ git clone https://github.com/neteasecloudmusicapienhanced/api-enhanced.git
$ cd api
$ pnpm i2. 运行
shell
# 默认端口 3000
$ node app.js服务器启动默认端口为 3000, 若不想使用 3000 端口 , 可使用以下命令 : Mac/Linux
shell
# 自定义端口(Mac/Linux)
PORT=4000 node app.jswindows 下使用 git-bash 或者 cmder 等终端执行以下命令
shell
# 自定义端口(Windows)
set PORT=4000 && node app.js服务器启动默认 host 为 localhost,如果需要更改, 可使用以下命令 : Mac/Linux
shell
$ HOST=127.0.0.1 node app.jswindows 下使用 git-bash 或者 cmder 等终端执行以下命令 :
shell
$ set HOST=127.0.0.1 && node app.js使用 npx 方式运行
支持 npx 方式运行,会自动安装依赖和运行
shell
npx @neteasecloudmusicapienhanced/api@版本号或者运行
shell
npx @neteasecloudmusicapienhanced/api@latest此命令每次执行都会使用最新版
Vercel 部署
v4.0.8 加入了 Vercel 配置文件,可以直接在 Vercel 下部署了,不需要自己的服务器(访问 Vercel 部署的接口,需要额外加一个 realIP 参数,如 /song/url?id=1969519579&realIP=116.25.146.177)
v4.29.9 加入了生成随机中国 IP 功能, 在请求时加上 randomCNIP=true 即可使用随机中国 IP, 如 /song/url?id=1969519579&randomCNIP=true
不能正常访问的,绑定下国内备案过的域名,之后即可正常访问
操作方法
- fork 此项目
- 在 Vercel 官网点击
New Project - 点击
Import Git Repository并选择你 fork 的此项目并点击import - 点击
PERSONAL ACCOUNT的select - 直接点
Continue PROJECT NAME自己填,FRAMEWORK PRESET选Other然后直接点Deploy接着等部署完成即可
腾讯云 serverless 部署
因Vercel在国内访问太慢(不绑定自己的域名的情况下),在此提供腾讯云 serverless 部署方法
操作方法
- fork 此项目
- 在腾讯云 serverless 应用管理页面( https://console.cloud.tencent.com/sls ),点击
新建应用 - 顶部
创建方式选择Web 应用 - 选择
Express框架,点击底部下一步按钮 - 输入
应用名,上传方式选择代码仓库,进行 GitHub 授权(如已授权可跳过这一步),代码仓库选择刚刚 fork 的项目 - 启动文件填入:
Shell
#!/bin/bash
export PORT=9000
/var/lang/node16/bin/node app.js- 点击
完成,等待部署完成,点击资源列表的API网关里的URL,正常情况会打开文档地址,点击文档例子可查看接口调用效果
- 注意
- 腾讯云 serverless 并不是免费的,前三个月有免费额度,之后收费
- 当前(2024-08-24), 用此法创建的话, 会
默认关联一个"日志服务-日志主题"(创建过程中没有提醒), 此服务是计量收费的, 若不需要, 可在腾讯云 serverless 应用管理页面( https://console.cloud.tencent.com/sls ), 点击应用名, 点击"日志服务"标签, 点击"日志主题", 点击"删除"即可删除
Docker 部署(已失效)
Docker 部署选项
1. 使用官方镜像
bash
# 拉取镜像(使用社区维护版本)
docker pull qiu8310/netease_cloud_music_api
# 运行容器
docker run -d -p 3000:3000 --name netease_api qiu8310/netease_cloud_music_api2. 自定义构建
创建 Dockerfile:
dockerfile
FROM node:18-alpine
WORKDIR /app
# 复制 package.json 并安装依赖
COPY package*.json ./
RUN npm ci --only=production
# 复制源代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["node", "app.js"]构建并运行:
bash
docker build -t my-netease-api .
docker run -d -p 3000:3000 --name my-netease-api my-netease-api3. Docker Compose
创建 docker-compose.yml:
yaml
version: '3.8'
services:
netease-api:
image: qiu8310/netease_cloud_music_api
ports:
- "3000:3000"
environment:
- PORT=3000
- HOST=0.0.0.0
restart: unless-stopped
# 限制资源使用
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'启动:
bash
docker-compose up -dPM2 进程管理
生产环境推荐
使用 PM2 确保服务稳定运行和自动重启:
bash
# 全局安装 PM2
npm install -g pm2
# 启动服务
pm2 start app.js --name "netease-api"
# 设置开机自启
pm2 startup
pm2 save
# 查看服务状态
pm2 status
# 查看日志
pm2 logs netease-api安全代理服务器实现
安全警告
绝对不要直接暴露 NeteaseCloudMusicApi 服务到公网! 必须通过代理服务器进行访问控制、身份验证和请求过滤。
为什么需要代理服务器?
- 防止滥用:限制只有你的网站可以调用 API
- 身份隔离:每个用户使用独立的登录会话
- 安全加固:添加额外的安全层,防止直接攻击
- 功能扩展:支持多种登录方式和人机验证处理
Node.js 代理服务器(推荐)
完整 Node.js 代理代码
javascript
// secure-proxy.js
const express = require('express');
const httpProxy = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
// ==================== 配置参数 ====================
// 允许访问的域名白名单(必须修改)
const ALLOWED_ORIGINS = [
'https://your-website.com',
'https://www.your-website.com',
'http://localhost:3000' // 开发环境
];
// 允许访问的 IP 白名单(可选)
const ALLOWED_IPS = [
// '192.168.1.0/24',
// '203.0.113.0/24'
];
// NeteaseCloudMusicApi 服务地址
const API_TARGET = 'http://localhost:3000';
// ==================== 创建应用 ====================
const app = express();
// ==================== 中间件配置 ====================
// 1. IP 白名单检查
const ipFilter = (req, res, next) => {
const clientIP = req.ip || req.connection.remoteAddress;
if (ALLOWED_IPS.length === 0) {
return next(); // 未配置 IP 白名单,跳过检查
}
const isAllowed = ALLOWED_IPS.some(allowedIP => {
if (allowedIP.includes('/')) {
// 处理 CIDR 格式(需要额外的库,这里简化处理)
return clientIP.startsWith(allowedIP.split('/')[0]);
}
return clientIP === allowedIP;
});
if (!isAllowed) {
return res.status(403).json({
code: 403,
message: 'IP not allowed',
timestamp: Date.now()
});
}
next();
};
// 2. CORS 配置
const corsOptions = {
origin: (origin, callback) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200
};
// 3. 请求频率限制
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟
max: 60, // 每个 IP 最多 60 次请求
message: {
code: 429,
message: 'Too many requests, please try again later.',
timestamp: Date.now()
},
standardHeaders: true,
legacyHeaders: false,
});
// 4. 安全头设置
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
// ==================== 路由配置 ====================
// 应用中间件
app.use(ipFilter);
app.use(cors(corsOptions));
app.use(limiter);
// 代理所有请求到 NeteaseCloudMusicApi
app.use('/', httpProxy({
target: API_TARGET,
changeOrigin: true,
secure: false,
onProxyReq: (proxyReq, req, res) => {
// 添加自定义请求头
proxyReq.setHeader('X-Forwarded-For', req.ip);
proxyReq.setHeader('X-Real-IP', req.ip);
},
onProxyRes: (proxyRes, req, res) => {
// 移除或修改响应头
delete proxyRes.headers['set-cookie']; // 防止 cookie 泄露
}
}));
// ==================== 错误处理 ====================
app.use((err, req, res, next) => {
if (err.message === 'Not allowed by CORS') {
return res.status(403).json({
code: 403,
message: 'CORS policy violation',
timestamp: Date.now()
});
}
console.error('Proxy error:', err);
res.status(500).json({
code: 500,
message: 'Internal server error',
timestamp: Date.now()
});
});
// ==================== 启动服务器 ====================
const PORT = process.env.PORT || 8080;
app.listen(PORT, '0.0.0.0', () => {
console.log(`Secure proxy server running on port ${PORT}`);
console.log(`Proxying to ${API_TARGET}`);
console.log(`Allowed origins: ${ALLOWED_ORIGINS.join(', ')}`);
});PHP 代理服务器(备选方案)
完整 PHP 代理代码
php
<?php
/**
* NeteaseCloudMusicApi 安全 PHP 代理服务器
*
* 安全特性:
* - 严格的域名/IP 白名单
* - 请求频率限制
* - HTTPS 强制重定向
* - 安全头设置
* - 错误日志记录
* - 防止 cookie 泄露
*/
// ==================== 安全检查 ====================
// 强制 HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
$redirectURL = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
header("HTTP/1.1 301 Moved Permanently");
header("Location: $redirectURL");
exit();
}
// 设置安全头
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
// ==================== 配置参数 ====================
// NeteaseCloudMusicApi 服务地址(修改为你的实际地址)
define('API_BASE_URL', 'http://localhost:3000');
// 允许访问的域名白名单(必须修改)
$ALLOWED_DOMAINS = [
'your-website.com',
'www.your-website.com',
'localhost' // 开发环境
];
// 允许访问的 IP 白名单(可选)
$ALLOWED_IPS = [
// '192.168.1.0/24',
// '203.0.113.0/24'
];
// 请求频率限制
define('RATE_LIMIT', 60); // 每分钟最大请求数
define('RATE_WINDOW', 60); // 时间窗口(秒)
// 日志配置
define('ENABLE_LOGGING', true);
define('LOG_FILE', __DIR__ . '/secure_proxy.log');
// ==================== 核心类 ====================
class SecureNeteaseProxy {
private $allowedDomains;
private $allowedIPs;
public function __construct($allowedDomains = [], $allowedIPs = []) {
$this->allowedDomains = $allowedDomains;
$this->allowedIPs = $allowedIPs;
$this->setCorsHeaders();
}
private function setCorsHeaders() {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($this->isAllowedOrigin($origin)) {
header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Credentials: true');
} else {
http_response_code(403);
echo json_encode(['code' => 403, 'message' => 'CORS policy violation']);
exit;
}
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
}
private function isAllowedOrigin($origin) {
if (empty($this->allowedDomains)) {
return true;
}
$host = parse_url($origin, PHP_URL_HOST);
return in_array($host, $this->allowedDomains);
}
private function isAllowedIP($ip) {
if (empty($this->allowedIPs)) {
return true;
}
foreach ($this->allowedIPs as $allowedIP) {
if ($this->matchIP($ip, $allowedIP)) {
return true;
}
}
return false;
}
private function matchIP($ip, $pattern) {
if (strpos($pattern, '/') !== false) {
// 简单的 CIDR 匹配(生产环境建议使用专门的库)
list($subnet, $mask) = explode('/', $pattern);
$subnet = ip2long($subnet);
$ipLong = ip2long($ip);
$mask = ~((1 << (32 - $mask)) - 1);
return ($ipLong & $mask) === ($subnet & $mask);
}
return $ip === $pattern;
}
private function getClientIP() {
$ipKeys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
foreach ($ipKeys as $key) {
if (!empty($_SERVER[$key])) {
$ip = trim(explode(',', $_SERVER[$key])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}
private function isRateLimited($ip) {
$cacheFile = sys_get_temp_dir() . "/rate_limit_$ip.json";
if (file_exists($cacheFile)) {
$data = json_decode(file_get_contents($cacheFile), true);
$now = time();
// 清理过期记录
$data = array_filter($data, function($timestamp) use ($now) {
return $timestamp > $now - RATE_WINDOW;
});
if (count($data) >= RATE_LIMIT) {
return true;
}
} else {
$data = [];
}
// 记录当前请求
$data[] = time();
file_put_contents($cacheFile, json_encode($data));
return false;
}
private function log($message, $level = 'INFO') {
if (!ENABLE_LOGGING) return;
$timestamp = date('Y-m-d H:i:s');
$ip = $this->getClientIP();
$logEntry = "[$timestamp] [$level] [IP: $ip] $message" . PHP_EOL;
file_put_contents(LOG_FILE, $logEntry, FILE_APPEND | LOCK_EX);
}
public function handleRequest() {
try {
$clientIP = $this->getClientIP();
// 检查 IP 白名单
if (!$this->isAllowedIP($clientIP)) {
throw new Exception('IP not allowed', 403);
}
// 检查请求频率
if ($this->isRateLimited($clientIP)) {
throw new Exception('Rate limit exceeded', 429);
}
// 获取请求路径
$path = $_GET['path'] ?? '';
if (empty($path)) {
throw new Exception('Missing path parameter', 400);
}
// 构建 API URL
$apiUrl = API_BASE_URL . '/' . ltrim($path, '/');
$queryParams = $_GET;
unset($queryParams['path']);
if (!empty($queryParams)) {
$apiUrl .= '?' . http_build_query($queryParams);
}
// 发送请求
$response = $this->makeSecureRequest($apiUrl);
$this->log("Request: $apiUrl - Response: " . strlen($response) . " bytes");
// 返回响应(移除敏感头)
header('Content-Type: application/json; charset=utf-8');
echo $response;
} catch (Exception $e) {
$this->handleError($e);
}
}
private function makeSecureRequest($url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false, // 禁用重定向
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_HTTPHEADER => [
'User-Agent: Mozilla/5.0 (compatible; NeteaseProxy/1.0)',
'Accept: application/json',
'X-Forwarded-For: ' . $this->getClientIP(),
'X-Real-IP: ' . $this->getClientIP()
],
CURLOPT_HEADERFUNCTION => function($curl, $header) {
// 过滤敏感响应头
if (stripos($header, 'set-cookie:') === 0) {
return 0; // 不处理 cookie 头
}
return strlen($header);
}
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new Exception("cURL Error: $error", 500);
}
if ($httpCode >= 400) {
throw new Exception("HTTP Error: $httpCode", $httpCode);
}
return $response;
}
private function handleError($exception) {
$code = $exception->getCode() ?: 500;
$message = $exception->getMessage();
$this->log("Error: $message", 'ERROR');
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'code' => $code,
'message' => $message,
'timestamp' => time()
], JSON_UNESCAPED_UNICODE);
}
}
// ==================== 执行代理 ====================
$proxy = new SecureNeteaseProxy($ALLOWED_DOMAINS, $ALLOWED_IPS);
$proxy->handleRequest();
?>代理服务器配置说明
1. 基本配置
javascript
// Node.js 代理配置
const ALLOWED_ORIGINS = [
'https://your-website.com', // 必须使用 HTTPS
'https://www.your-website.com',
'http://localhost:3000' // 开发环境
];
const API_TARGET = 'http://localhost:3000'; // 内网地址,不对外暴露php
// PHP 代理配置
define('API_BASE_URL', 'http://localhost:3000'); // 内网地址
$ALLOWED_DOMAINS = [
'your-website.com',
'www.your-website.com'
];2. 安全部署
bash
# 设置文件权限
chmod 600 secure-proxy.js # Node.js
chmod 600 secure_proxy.php # PHP
# 创建日志目录
mkdir -p /var/log/netease-proxy
chmod 700 /var/log/netease-proxy
# 配置防火墙(仅允许必要端口)
ufw allow 8080/tcp # 代理服务器端口
ufw deny 3000 # 禁止直接访问 API 端口登录方式详解
1. 手机号密码登录
API 接口
POST /login/cellphone参数说明
| 参数 | 类型 | 必选 | 说明 |
|---|---|---|---|
| phone | string | 是 | 手机号码 |
| password | string | 是 | 密码 |
| countrycode | number | 否 | 国家区号,默认 86(中国) |
安全注意事项
- 密码加密:前端应使用 HTTPS 传输,不要在客户端存储明文密码
- 会话管理:登录成功后,将 cookie 存储在安全的 HTTP-only cookie 中
- 频率限制:对登录接口实施严格的频率限制(如每 5 分钟 3 次)
2. 二维码登录
登录流程
- 获取二维码 key:
GET /login/qr/key - 生成二维码:
https://music.163.com/login?codekey={key} - 轮询检查状态:
GET /login/qr/check?key={key} - 获取登录结果
安全实现
javascript
// 二维码登录安全实现
class SecureQRLogin {
async getQRKey() {
// 通过安全代理获取 key
const response = await fetch('/secure-proxy/login/qr/key', {
credentials: 'include' // 包含 cookie
});
return response.json();
}
async checkQRStatus(key) {
// 添加防爆破保护
const response = await fetch(`/secure-proxy/login/qr/check?key=${encodeURIComponent(key)}`, {
credentials: 'include'
});
return response.json();
}
// 实现轮询逻辑(带超时和错误处理)
async startPolling(key, onStatusChange, timeout = 120000) {
const startTime = Date.now();
const poll = async () => {
if (Date.now() - startTime > timeout) {
throw new Error('QR code expired');
}
try {
const data = await this.checkQRStatus(key);
onStatusChange(data);
if (data.code === 803) {
// 登录成功
return data;
} else if (data.code === 800) {
// 二维码过期
throw new Error('QR code expired');
}
// 继续轮询
setTimeout(poll, 2000);
} catch (error) {
console.error('Polling error:', error);
throw error;
}
};
return poll();
}
}3. VIP 账号使用
重要说明
要播放 VIP 歌曲,必须使用已登录 VIP 账号的会话。不要在公共代理中使用 VIP 账号,这会导致账号被共享和滥用。
安全的 VIP 使用方案
- 个人专用代理:为每个用户创建独立的代理实例
- 会话隔离:使用不同的 cookie 存储空间
- 账号保护:定期更换密码,监控异常登录
javascript
// 个人专用代理配置示例
const personalProxy = express();
// 为每个用户创建独立的 cookie jar
const userCookieJars = new Map();
personalProxy.use('/api', async (req, res) => {
const userId = req.headers['x-user-id']; // 从请求头获取用户 ID
const cookieJar = userCookieJars.get(userId) || new Map();
// 转发请求并使用用户的 cookie
const apiResponse = await forwardRequestWithCookies(req, cookieJar);
// 保存新的 cookie
if (apiResponse.headers['set-cookie']) {
cookieJar.set('netease_cookie', apiResponse.headers['set-cookie']);
userCookieJars.set(userId, cookieJar);
}
res.json(apiResponse.body);
});安全最佳实践
1. 网络隔离
用户浏览器(HTTPS)───▶安全代理服务器(HTTPS)───▶NeteaseCloudMusicApi (HTTP (内网))- 代理服务器:暴露在公网,处理所有安全检查
- API 服务:仅在内网运行,不直接对外提供服务
2. 身份验证
- API 密钥:为每个客户端分配唯一的 API 密钥
- JWT 令牌:使用 JWT 进行用户身份验证
- OAuth 2.0:集成第三方登录
3. 监控和日志
javascript
// 添加详细的日志记录
app.use((req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
console.log({
timestamp: new Date().toISOString(),
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get('User-Agent')
});
});
next();
});4. 定期安全审计
- 依赖更新:定期更新 Node.js 和 npm 依赖
- 漏洞扫描:使用
npm audit或snyk扫描漏洞 - 配置审查:定期检查安全配置是否正确
故障排除
1. 常见问题
301 重定向错误
问题: API 返回 301 重定向错误
原因: 未正确处理 cookie 或登录状态
解决方案:
- 确保在请求中包含正确的 cookie
- 检查是否启用了缓存(API 默认缓存 2 分钟)
- 在请求 URL 中添加时间戳参数:
?timestamp=${Date.now()}
VIP 歌曲无法播放
问题: VIP 歌曲返回空的播放地址
原因:
- 未使用 VIP 账号登录
- 账号 VIP 已过期
- 歌曲版权限制
解决方案:
- 确认账号是有效的 VIP 账号
- 重新登录 VIP 账号
- 检查歌曲的
fee字段(8 表示 VIP 专享)
2. 性能优化
缓存策略
javascript
// 智能缓存实现
const cache = new Map();
function getCachedData(key, ttl = 300000) { // 5分钟
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
return null;
}
function setCachedData(key, data) {
cache.set(key, {
data: data,
timestamp: Date.now()
});
// 清理过期缓存
if (cache.size > 1000) {
const keys = Array.from(cache.keys());
for (let i = 0; i < 100; i++) {
cache.delete(keys[i]);
}
}
}总结
本指南提供了 NeteaseCloudMusicApi 的安全部署方案,重点强调了:
安全要点
- 绝不直接暴露 API 服务:必须通过安全代理
- 严格的访问控制:域名/IP 白名单 + 频率限制
- HTTPS 全程加密:从用户到代理再到 API
- 会话隔离:防止账号信息泄露
- VIP 账号保护:避免在公共环境中使用
