Files
StarlightRadio/HANDOFF.md
T
Xiao-no-love 789ee50391 docs: HANDOFF 精简重写 (v2.6 交接版)
- 版本流水账压缩为时间线表
- 补充 v2.5/v2.6 权限模型与 API
- 工具坑更新 powershell 行号偏移教训
2026-09-25 22:43:40 +08:00

209 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交接文档 · 音乐在线电台
> 跨会话交接。**新会话先读此文件再动手**。最后更新:2026-09-25(v2.6)。
---
## 0. TL;DR
- **项目**:`F:\music_online\demo` — Node.js + FFmpeg 的 HLS 个人音乐电台
- **状态**:v2.6,功能完整。回归测试 **api 16 / e2e 17 / write 5** 全绿
- **服务**:`svc003` 运行中,`http://127.0.0.1:3000`
- **密码**:管理员 `test123` / 用户 `user123`(start.cmd 里设的环境变量,测试用)
- **Git**:`a6a01b7`(v2.5) → `2fc2548`(v2.6 最新)。工作区干净。
---
## 1. 运行 / 环境
- Windows 11,Node v24.5.0,FFmpeg 9.0.1(`F:\ffmpge\ffmpeg-9.0.1-essentials_build\bin`,含 ffprobe),Python 3.11
- **启动**(推荐托管):
```cmd
cmd /c "set ADMIN_PASSWORD=test123&& set USER_PASSWORD=user123&& node index.js"
```
或双击 `start.cmd`(已配好双密码)
- 登录:浏览器开 `http://127.0.0.1:3000`
- 依赖:`better-sqlite3`、`multer`、`chokidar`、`express`(已装)
---
## 2. 目录结构
```
demo/
├── index.js # 入口,仅 require("./src/server").start()
├── start.cmd # 启动脚本(含 ADMIN_PASSWORD/USER_PASSWORD)
├── data.db # SQLite(WAL,自动生成)
├── HANDOFF.md / README.md
├── src/
│ ├── server.js # 装配:中间件/路由/启动/优雅退出
│ ├── config.js # 全部配置(环境变量解析)
│ ├── logger.js # 分级日志 + 落盘 + 内存环形缓冲(tail)
│ ├── db.js # SQLite init + schema(5表) + WAL + 运行时迁移
│ ├── store.js # 数据访问层,所有 SQL 收敛于此
│ ├── util.js # fileHash/文件名解析/原子写/目录大小
│ ├── media.js # ffprobe + FFmpeg 切片 + 并发队列 + 进程追踪
│ ├── metadata.js # 网易云 API + 封面下载 + 元数据补全
│ ├── livestream.js # LiveStream 类 + 多路流 + 缓存状态/清理
│ ├── scanner.js # audio/ 监听 + 同步 DB
│ ├── auth.js # 双密码鉴权 + 角色 + 会话 + 登录锁定
│ ├── sse.js # SSE 广播
│ └── routes/ # auth / music / resources / streams / system
├── public/
│ ├── index.html # 主面板(6 tab)
│ ├── login.html # 登录页(独立暗色主题)
│ ├── css/style.css # token 体系(v2.4 重构)
│ ├── js/app.js # 前端逻辑
│ ├── vendor/ # 本地化:bootstrap/icons/sortable/hls
│ └── covers/ # 本地封面
├── audio/ hls/ hls_cache/ # 音频 / 直播输出 / 切片缓存(均 gitignore)
├── scripts/fetch_vendor.py
└── test_api.py / test_e2e.py / test_write.py / test_enrich.py
```
---
## 3. 数据模型(SQLite, src/db.js)
```sql
songs(id, file UNIQUE, name, artist, cover, cover_remote, duration,
file_size, heat, cache_status, created_at)
-- file 存「文件名」非绝对路径;cover 本地路径;cover_remote 远程 URL(优先展示)
streams(id PK, name, enabled, current_pos, media_seq, created_at)
-- 默认流 id='main',不可删
stream_playlist(id PK, stream_id, song_id, position) -- 外键 CASCADE
settings(key PK, value)
sessions(token PK, role, created_at, expires_at) -- role: admin|user
```
- 切片缓存 `hls_cache/{md5(文件名)}/` 与流无关,多路共享
- **运行时迁移**:init() 用 `PRAGMA table_info` 检测缺列后 ALTER(已支持 sessions.role、songs.cover_remote)
---
## 4. API 清单
除 `/api/auth/*`、`/hls`、`/hls_cache`、静态资源、`/login` 外**全部需登录**。
🔒 = 需**管理员**。
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/auth/login` | `{password}` → `{ok,role}`;失败过多返回 429 |
| POST | `/api/auth/logout` · GET `/api/auth/me` | 登出 / 返回 `{authed,role}` |
| GET | `/api/resources` | 资源列表(含 albumImage/coverLocal/cacheSize) |
| GET | `/api/music/search?keyword=` | 网易云搜索(后端**缓存+限流**) |
| POST | `/api/music/detail` · `/download` | 详情 / 下载入库 |
| POST | `/api/music/import` 🔒 | **multipart**:audio+title+artist |
| POST | `/api/music/delete` · `/delete-batch` | 级联删 |
| POST | `/api/music/enrich` | `{file,skipMatch?}` 重匹配元数据 |
| POST | `/api/music/refresh-covers` 🔒 | **一键刷新所有缺失封面** |
| GET/POST/DELETE | `/api/streams[/:id]` · `/start\|stop` | 流管理 |
| GET/POST | `/api/streams/:id/playlist[/add\|remove\|reorder\|clear\|next\|prev\|jump]` | 播放列表 |
| GET | `/api/system/cache` 🔒 · POST `/cache/cleanup` 🔒 | 缓存 |
| GET | `/api/system/logs?tail=` 🔒 · `/config` 🔒 · `/settings` 🔒 | 系统 |
| GET | `/api/events` | SSE(cookie 鉴权) |
统一返回 `{ok:true,...}` / `{ok:false,error:"..."}`。
---
## 5. 直播流地址
- 默认 `/hls/main/index.m3u8`;其它 `/hls/{streamId}/index.m3u8`
- 切片 `/hls_cache/{hash}/indexN.ts`
---
## 6. 关键设计决策(勿轻易推翻)
1. **SQLite better-sqlite3 + WAL**:单进程同步 API 最省事,WAL 解读写互斥
2. **不迁 v1 JSON 数据**(旧 heat/metadata/playlist.json 已删)
3. **鉴权整站 Cookie 会话**;HLS 流**不锁**(播放器难带 cookie)
4. **封面/资源全本地化**:`public/covers/`、`public/vendor/`;系统字体
5. **songs.file 存文件名**非绝对路径,可移植
6. **导入先落 tmp 再 rename**,避免 chokidar 扫到半截
7. **双密码角色**:用户 = 管理员 − 上传 − 设置页;后端 `requireAdmin` 硬挡,非只藏前端
---
## 7. 测试方法
服务起来后:
```cmd
cd /d F:\music_online\demo
set PYTHONIOENCODING=utf-8
python -X utf8 test_api.py # 16 项
python -X utf8 test_e2e.py # 17 项
python -X utf8 test_write.py # 5 项(需 tmp/test_import.mp3 素材)
```
素材生成:
```cmd
"F:\ffmpge\ffmpeg-9.0.1-essentials_build\bin\ffmpeg.exe" -y -f lavfi -i anullsrc=r=44100:cl=stereo -t 3 -q:a 9 tmp\test_import.mp3
```
> ⚠️ 测试只覆盖 HTTP 层,**浏览器 JS 交互未自动化**。
---
## 8. 已知问题 / 待实测
- **浏览器端未实测**:SSE 实时刷新、拖拽 reorder、mini player、上传进度、频道弹窗交互
- `metadata.js enrichSong` 启动重匹配慢歌 → 可接受
- 多路流并发预生成排队 → 观察中
- `app.js esc()` 用 `String.fromCharCode(38)` 绕过工具转义,**勿优化**
- 登录失败锁定 `failMap` 是内存态,重启清零(可接受)
---
## 9. 工具层踩坑记录
1. **XML 实体转义吃 `&` `"`**:写 JS/CSS 用 `String.fromCharCode(38)` 或字符码构造
2. **⚠️ powershell 行号偏移**:`Select-String`/`Get-Content` 行号比 replace_lines 实际大 ~2,照它改必错位!**用 node 查行号**:
```js
fs.readFileSync(f,"utf8").split("\n").forEach((l,i)=>{if(l.includes(p))console.log((i+1)+": "+l)})
```
3. **replace_lines 多段连锁崩**:同一文件多段改,某段行号错会留残留。**逐条改**,改前 node 查号、改后 `node --check`
4. **文件被改乱**:别手修,`git checkout <commit> -- <file>` 恢复
5. **后台服务用 action background="true"**(勿 `start /b`)
6. **service_stop 连子进程一起杀**(含 node 的 ffmpeg 子进程)
7. **改完源码要重启 node** 才生效
8. **Windows 无 head/tail**,用 findstr 或 node
9. **cmd 下 curl**:`%{http_code}` 要写 `%%{http_code}`;中文输出 GBK 乱码(功能正常)
10. **git 提交中文**:powershell 写**无 BOM** UTF-8 文件 + `git -c i18n.commitEncoding=utf-8 commit -F 文件`
11. **python 输出**:`chcp 65001` + `set PYTHONIOENCODING=utf-8`;脚本末尾 `sys.stdout.flush()` 再 `os._exit(0)`
---
## 10. 版本时间线
| 版本 | 日期 | 要点 |
|------|------|------|
| v2 | 09-23 | SQLite+多路流+鉴权+导入/删除+资源本地化+模块化重构 |
| v2.1 | 09-23 | 频道管理模态框、死代码清理、forgetSong 内存泄漏修复 |
| v2.2 | 09-24 | 站内 HLS 收听页、设置页、上传进度、正在播放实时化 |
| v2.2.1 | 09-24 | 资源排序(6 种) |
| v2.3 | 09-24 | UI 去 AI 味:清全部渐变/glow,单强调色(依 ibelick/ui-skills) |
| v2.4 | 09-24 | UI token 化:字号 42→9、圆角 20→8、硬编码色清零 |
| v2.5 | 09-25 | **双密码+角色权限**、登录锁定、搜索缓存限流、**封面远程优先** |
| v2.6 | 09-25 | 设置页**一键刷新缺失封面** |
各版本详细变更见 git log(`git log --oneline`)。
---
## 11. 下一步可选
1. **浏览器手动过一遍**所有交互,修 Console 报错 ← 最优先
2. 播放延迟调节(HLS_TIME 热更新,老板曾暂缓)
3. 密码设置 UI(现走环境变量)
4. 缓存生成进度条、搜索分页/加载更多
5. mini player 音量控制、播放历史/最热排序(heat 字段已有)
6. 正式化:固定强密码、服务化启动
---
## 12. 当前运行态
- 后台服务:`svc003`(`http://127.0.0.1:3000`)
- DB:`data.db`(6 首歌曲,封面已本地化)
- 缓存:`hls_cache/` 约 34MB
- Git:`2fc2548` 干净