Skip to content

Commit 826a979

Browse files
committed
feat: add cookie caching mechanism and environment variable support
- Add in-memory cache for cookie files (5 min TTL) - Support reading cookies from environment variables (METING_COOKIE_*) - Add file watcher to auto-clear cache on cookie file changes - Priority: environment variable > file storage - Update README with cookie configuration documentation
1 parent 1f1ed5d commit 826a979

File tree

4 files changed

+129
-6
lines changed

4 files changed

+129
-6
lines changed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ docker run -d \
149149
| `SSL_CERT_PATH` | HTTPS 证书文件路径 | - |
150150
| `METING_URL` | API 服务的公网访问地址(用于生成回调 URL) | - |
151151
| `METING_TOKEN` | HMAC 签名密钥 | `token` |
152+
| `METING_COOKIE_ALLOW_HOSTS` | 允许使用 cookie 的 referrer 域名白名单(逗号分隔) | `` (空,不限制) |
153+
| `METING_COOKIE_NETEASE` | 网易云音乐 Cookie | - |
154+
| `METING_COOKIE_TENCENT` | QQ音乐 Cookie | - |
155+
| `METING_COOKIE_KUGOU` | 酷狗音乐 Cookie | - |
156+
| `METING_COOKIE_XIAMI` | 虾米音乐 Cookie | - |
157+
| `METING_COOKIE_BAIDU` | 百度音乐 Cookie | - |
158+
| `METING_COOKIE_KUWO` | 酷我音乐 Cookie | - |
152159

153160
## API 接口文档
154161

@@ -256,6 +263,60 @@ const token = generateToken('netease', 'url', '123456');
256263
- `miss`:缓存未命中,调用上游 API
257264
- 无此头:缓存命中
258265

266+
## Cookie 配置
267+
268+
部分音乐平台的 API 需要登录态才能访问完整数据。可以通过以下两种方式配置 Cookie:
269+
270+
### 方式一:环境变量(推荐)
271+
272+
通过环境变量 `METING_COOKIE_大写平台名` 配置:
273+
274+
```bash
275+
# Docker 部署示例
276+
docker run -d \
277+
-p 80:80 \
278+
-e METING_COOKIE_NETEASE="your_netease_cookie" \
279+
-e METING_COOKIE_TENCENT="your_tencent_cookie" \
280+
--name meting-api \
281+
meting-api
282+
```
283+
284+
### 方式二:文件存储
285+
286+
在项目根目录 `cookie/` 文件夹下创建以平台名命名的文件(无扩展名):
287+
288+
```
289+
cookie/
290+
├── netease # 网易云音乐 Cookie
291+
├── tencent # QQ音乐 Cookie
292+
├── kugou # 酷狗音乐 Cookie
293+
└── ...
294+
```
295+
296+
每个文件存储对应平台的 Cookie 字符串。
297+
298+
### Cookie 优先级
299+
300+
1. 优先从环境变量读取(`METING_COOKIE_NETEASE` 等)
301+
2. 环境变量不存在时从文件读取(`cookie/netease` 等)
302+
303+
### Cookie 缓存
304+
305+
- Cookie 内容会在内存中缓存 5 分钟,减少文件系统读取
306+
- 使用文件存储时,修改 cookie 文件会自动清除缓存,立即生效
307+
- 环境变量方式需要重启服务才能更新
308+
309+
### Referrer 白名单
310+
311+
通过 `METING_COOKIE_ALLOW_HOSTS` 环境变量限制哪些来源可以使用 Cookie:
312+
313+
```bash
314+
# 仅允许特定域名使用 Cookie
315+
METING_COOKIE_ALLOW_HOSTS=example.com,music.example.com
316+
```
317+
318+
不设置时不限制来源。这可以防止 Cookie 被第三方滥用。
319+
259320
## 错误处理
260321

261322
API 返回标准 HTTP 状态码:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@meting/server",
3-
"version": "1.8.1",
3+
"version": "1.9.0",
44
"main": "src/index.js",
55
"type": "module",
66
"author": "metowolf",

src/config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ export default {
2727
url: process.env.METING_URL || '',
2828
token: process.env.METING_TOKEN || 'token',
2929
cookie: {
30-
allowHosts: process.env.METING_COOKIE_ALLOW_HOSTS ?
31-
(process.env.METING_COOKIE_ALLOW_HOSTS).split(',').map(h => h.trim().toLowerCase()) :
32-
[]
30+
allowHosts: process.env.METING_COOKIE_ALLOW_HOSTS
31+
? (process.env.METING_COOKIE_ALLOW_HOSTS).split(',').map(h => h.trim().toLowerCase())
32+
: []
3333
}
3434
}
3535
}

src/utils/cookie.js

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,81 @@
1-
import { readFile } from 'node:fs/promises'
1+
import { readFile, watch } from 'node:fs/promises'
22
import { resolve } from 'node:path'
33
import { URL } from 'node:url'
44
import config from '../config.js'
55

6+
// Cookie 缓存
7+
const cookieCache = new Map()
8+
const COOKIE_TTL = 1000 * 60 * 5 // 5分钟缓存过期
9+
10+
// 启动文件监听
11+
const cookieDir = resolve(process.cwd(), 'cookie')
12+
let watcher = null
13+
14+
async function startWatcher () {
15+
try {
16+
watcher = watch(cookieDir)
17+
for await (const event of watcher) {
18+
if (event.filename) {
19+
// 文件变化时清除对应缓存
20+
cookieCache.delete(event.filename)
21+
}
22+
}
23+
} catch (error) {
24+
// 监听失败不影响正常运行
25+
}
26+
}
27+
28+
// 启动监听(仅启动一次)
29+
if (!watcher) {
30+
startWatcher().catch(() => {})
31+
}
32+
633
/**
734
* 读取指定平台的 cookie 文件
835
* @param {string} server - 平台名称 (netease, tencent 等)
936
* @returns {Promise<string>} cookie 字符串,失败时返回空字符串
1037
*/
1138
export async function readCookieFile (server) {
39+
const now = Date.now()
40+
const cached = cookieCache.get(server)
41+
42+
// 检查缓存是否有效
43+
if (cached && now - cached.timestamp < COOKIE_TTL) {
44+
return cached.value
45+
}
46+
47+
// 优先从环境变量读取
48+
const envKey = `METING_COOKIE_${server.toUpperCase()}`
49+
const envCookie = process.env[envKey]
50+
if (envCookie) {
51+
const value = envCookie.trim()
52+
// 更新缓存
53+
cookieCache.set(server, {
54+
value,
55+
timestamp: now
56+
})
57+
return value
58+
}
59+
60+
// 从文件读取
1261
try {
1362
const cookiePath = resolve(process.cwd(), 'cookie', server)
1463
const cookie = await readFile(cookiePath, 'utf-8')
15-
return cookie.trim()
64+
const value = cookie.trim()
65+
66+
// 更新缓存
67+
cookieCache.set(server, {
68+
value,
69+
timestamp: now
70+
})
71+
72+
return value
1673
} catch (error) {
74+
// 读取失败时也缓存空字符串,避免频繁读取不存在的文件
75+
cookieCache.set(server, {
76+
value: '',
77+
timestamp: now
78+
})
1779
return ''
1880
}
1981
}

0 commit comments

Comments
 (0)