docs: 添加交接文档 HANDOFF.md
This commit is contained in:
1 parent
f96a4bae51
commit
4510d697f0
1 file changed
+247
+247
@@ -0,0 +1,247 @@
|
||||
# 交接文档 · 音乐在线电台 v2
|
||||
|
||||
> 本文件用于跨会话交接。上下文耗尽后,新会话**先读此文件**再动手。
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR
|
||||
|
||||
- **项目**:`F:\music_online\demo` — Node.js + FFmpeg 的 HLS 音乐电台
|
||||
- **状态**:v2 重构**基本完成**,后端 38/38 测试通过;**但用户反馈"功能还有问题",具体未明**(见 §9 待排查)
|
||||
- **服务**:正在运行 `svc005`,`http://127.0.0.1:3000`,密码 `test123`(测试用)
|
||||
- **Git**:`ea5aa0c`(基线) → `b8e9bf3`(v2主体) → `a4745db`(收尾)
|
||||
|
||||
---
|
||||
|
||||
## 1. 运行 / 环境
|
||||
|
||||
- 环境:Windows 11,Node v24.5.0,npm 11.5.1,FFmpeg 9.0.1(含 ffprobe,均在 PATH),Python 3.11
|
||||
- 启动:
|
||||
```cmd
|
||||
cd /d F:\music_online\demo
|
||||
set PANEL_PASSWORD=test123 && node index.js
|
||||
```
|
||||
- 依赖:`better-sqlite3`、`multer`、`chokidar`、`express`(已装)
|
||||
- 登录:浏览器开 `http://127.0.0.1:3000`,输入密码
|
||||
|
||||
---
|
||||
|
||||
## 2. 目录结构(v2)
|
||||
|
||||
```
|
||||
demo/
|
||||
├── index.js # 入口,仅 require("./src/server").start()
|
||||
├── package.json # v2.0.0
|
||||
├── data.db # SQLite(自动生成,WAL)
|
||||
├── HANDOFF.md # 本文件
|
||||
├── README.md # v2 说明
|
||||
├── src/
|
||||
│ ├── server.js # 装配:中间件、路由、启动、优雅退出
|
||||
│ ├── config.js # 所有配置(环境变量解析)
|
||||
│ ├── logger.js # 分级日志 + 落盘 + 内存环形缓冲(tail)
|
||||
│ ├── db.js # SQLite 初始化 + 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 # Cookie 会话鉴权 + requireAuth 中间件
|
||||
│ ├── sse.js # Server-Sent Events 广播
|
||||
│ └── routes/
|
||||
│ ├── auth.js # /api/auth/*
|
||||
│ ├── music.js # /api/music/* (搜索/详情/下载/导入/删除/enrich)
|
||||
│ ├── resources.js # /api/resources
|
||||
│ ├── streams.js # /api/streams/* (流管理 + 播放列表)
|
||||
│ └── system.js # /api/system/* (缓存/日志/配置/设置)
|
||||
├── public/
|
||||
│ ├── index.html # 主面板
|
||||
│ ├── login.html # 登录页(新)
|
||||
│ ├── css/style.css # 含 v2 追加样式(.stream-select/.import-card/.now-badge)
|
||||
│ ├── js/app.js # v2 重写:多路流/鉴权/导入/删除
|
||||
│ ├── vendor/ # 本地化:bootstrap/icons/sortable + woff2
|
||||
│ ├── covers/ # 本地化封面(每首一个 jpg)
|
||||
│ └── favicon.ico/.png
|
||||
├── scripts/fetch_vendor.py # 一次性下载前端 CDN 资源(已跑过)
|
||||
├── audio/ # 音频(5 首 mp3,已 gitignore)
|
||||
├── hls/ # 直播输出 hls/{streamId}/index.m3u8
|
||||
├── hls_cache/ # 切片缓存 hls_cache/{fileHash}/
|
||||
├── tmp/uploads/ # multer 上传临时目录
|
||||
└── 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, duration, file_size,
|
||||
heat, cache_status, created_at)
|
||||
-- file 存「文件名」不是绝对路径(可移植)
|
||||
-- cover 存 web 路径如 /covers/xxx.jpg
|
||||
|
||||
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, created_at, expires_at)
|
||||
```
|
||||
|
||||
**关键**:切片缓存 `hls_cache/{md5(文件名)}/` 与「流」无关,多路共享,省空间。
|
||||
|
||||
---
|
||||
|
||||
## 4. API 清单
|
||||
|
||||
除 `/api/auth/*`、`/hls`、`/hls_cache`、静态资源、`/login` 外,**全部需登录**。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/api/auth/login` | `{password}` → 设 cookie |
|
||||
| POST | `/api/auth/logout` | 登出 |
|
||||
| GET | `/api/auth/me` | 登录状态 |
|
||||
| GET | `/api/resources` | 资源列表(含 cacheSize/cover) |
|
||||
| GET | `/api/music/search?keyword=` | 网易云搜索 |
|
||||
| POST | `/api/music/detail` | `{id}` |
|
||||
| POST | `/api/music/download` | `{id,name,artist,albumImage}` |
|
||||
| POST | `/api/music/import` | **multipart**:audio + title + artist |
|
||||
| POST | `/api/music/delete` | `{file}` 级联删 |
|
||||
| POST | `/api/music/delete-batch` | `{files:[]}` |
|
||||
| POST | `/api/music/enrich` | `{file, skipMatch?}` 重匹配元数据 |
|
||||
| GET | `/api/streams` | 流列表 |
|
||||
| POST | `/api/streams` | `{id,name}` 创建 |
|
||||
| DELETE | `/api/streams/:id` | 删除(main 不可删) |
|
||||
| POST | `/api/streams/:id/start\|stop` | 启停 |
|
||||
| GET | `/api/streams/:id/playlist` | 播放列表(含 entryId/isCurrent) |
|
||||
| POST | `/api/streams/:id/playlist/add` | `{file}` |
|
||||
| POST | `/api/streams/:id/playlist/remove` | `{entryId}` |
|
||||
| POST | `/api/streams/:id/playlist/reorder` | `{order:[entryId...]}` |
|
||||
| POST | `/api/streams/:id/playlist/clear\|next\|prev\|jump` | |
|
||||
| GET | `/api/system/cache` | 缓存占用 |
|
||||
| POST | `/api/system/cache/cleanup` | 手动清理 |
|
||||
| GET | `/api/system/logs?tail=` | 日志 |
|
||||
| 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 数据**:用户明确说旧数据不要。
|
||||
3. **鉴权选"中档"**:整站 Cookie 会话登录,面板+API 锁,**HLS 流不锁**(播放器难带 cookie)。
|
||||
4. **封面/资源全本地化**:`public/covers/` 存封面,`public/vendor/` 存前端库;系统字体替代 Google Fonts。
|
||||
5. **songs.file 存文件名**:不是绝对路径,迁移目录不失效。
|
||||
6. **导入先落 tmp 再 rename**:避免 chokidar 扫到半截文件。
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试方法
|
||||
|
||||
服务起来后(svc005 / 或新起)跑:
|
||||
|
||||
```cmd
|
||||
cd /d F:\music_online\demo
|
||||
set PYTHONIOENCODING=utf-8
|
||||
python test_api.py # 16 项:鉴权/资源/流/缓存/日志
|
||||
python test_e2e.py # 17 项:静态资源/登录/首页/HTML元素
|
||||
python test_write.py # 5 项:导入 + 删除(用 tmp 副本,不动原曲)
|
||||
python test_enrich.py # 给 5 首补封面(已跑过,5/5 成功)
|
||||
```
|
||||
|
||||
上次全绿:**38/38**。但**注意**:这些只测了 HTTP 层,**浏览器端 JS 交互没自动化测过**。
|
||||
|
||||
---
|
||||
|
||||
## 8. 已完成清单
|
||||
|
||||
- [x] SQLite 替换 JSON(5 表 + WAL)
|
||||
- [x] 级联删除 `/api/music/delete` + 批量
|
||||
- [x] 多路流 LiveStream 类 + 每路独立播放列表
|
||||
- [x] Cookie 会话鉴权 + 登录页 + 鉴权绕过漏洞修复
|
||||
- [x] 本地上传导入(multer)+ 元数据自动匹配
|
||||
- [x] 封面本地化 + vendor 本地化 + 系统字体
|
||||
- [x] 日志系统(分级/落盘/面板可查)
|
||||
- [x] API 统一返回结构
|
||||
- [x] 优雅退出补 FFmpeg 子进程 kill
|
||||
- [x] 模块化拆分(1233 行单文件 → src/ 12 模块)
|
||||
- [x] 前端 app.js 重写(多路流/鉴权/导入/删除)
|
||||
|
||||
---
|
||||
|
||||
## 9. ⚠️ 待排查 / 已知问题(重点!)
|
||||
|
||||
### 9.1 用户反馈"功能还有问题" —— **具体未明,需先问清楚**
|
||||
> 下一会话**第一件事**:问用户到底哪个功能出问题(现象、复现步骤、控制台报错)。
|
||||
> 可能的方向见下。
|
||||
|
||||
### 9.2 前端多路流管理 UI **没做**
|
||||
- 后端能创建/删除流,但前端只有一个**切换下拉框**(`#streamSelect`),**没有"新建流/删除流"的界面**。
|
||||
- 若用户说"多路流用不了",大概率指这个。
|
||||
|
||||
### 9.3 前端 JS 交互未实测
|
||||
- `public/js/app.js` 是重写的,只做了语法检查 + HTTP 层测试。
|
||||
- **未验证**:SSE 实时刷新、拖拽排序 reorder、mini player、页面切换、导入进度提示等浏览器行为。
|
||||
- 建议:手动开浏览器点一遍,看 Console 有无报错。
|
||||
|
||||
### 9.4 潜在 Bug 点(未确认)
|
||||
- `livestream.js` 的 `enforceCacheSizeLimit` 依赖 `streams` Map,若在 `loadStreams` 前被调用可能拿到空集。
|
||||
- `scanner.js` 里 `const parsed = meta.sanitizeFileName;` 是**死代码**(无用变量),无害但可清理。
|
||||
- `metadata.js` 的 `enrichSong` 每次启动会对缺元数据的歌重新请求网易云,网络慢会拖启动。
|
||||
- 多路流同时播放不同歌时,FFmpeg 预生成队列(并发默认 3)可能跟不上,需观察。
|
||||
- `app.js` 里 `esc()` 用了 `String.fromCharCode(38)` 拼接,是**为了绕过工具层的 XML 实体转义**,不要"优化"回字面量 `&`,否则写文件时会被工具吃掉。
|
||||
|
||||
### 9.5 前端 HTML 元素 ID 对照(改前端时用)
|
||||
- 头部:`cacheInfo` `streamSelect` `liveLink` `btnLogout`
|
||||
- 搜索页:`musicId` `btnSearch` `parseHint` `parsedId` `musicPreview` `musicCover` `musicName` `musicArtist` `musicDetails` `btnDownload` `searchResults` `searchTips`
|
||||
- 导入卡片:`importCard` `importFile` `importTitle` `importArtist` `btnImport` `importProgress`
|
||||
- 资源页:`resourceCount` `resourceSearch` `resourceList`
|
||||
- 列表页:`playlistCount` `btnClear` `playlistList`
|
||||
- mini player:`miniPlayer` `mpProgress` `mpBody` `mpCover` `mpName` `mpArtist` `mpTime` `mpPlay` `mpExpand` `musicPlayer`
|
||||
- 导航:`.nav-tab[data-page]`,页面 `pageSearch/pageResources/pagePlaylist/pageAbout`
|
||||
|
||||
---
|
||||
|
||||
## 10. 工具层踩坑记录(写代码时注意)
|
||||
|
||||
1. **XML 实体转义会吃掉 `&` `"`**:写 JS/CSS 含这些字面量时,用 `String.fromCharCode(38)` 或字符码构造,别直接写。
|
||||
2. **replace_lines 多段锚点行号易偏**:先 `file_read` / python 打印实际行号再改;改错了整批不生效或误删。
|
||||
3. **smart_patch 行数必须精确**,且锚点行号要对;容易失败,简单改动优先 replace_lines。
|
||||
4. **后台服务用 action background="true"**(不要 `start /b`,会粘连卡死)。
|
||||
5. **service_stop 会连子进程一起杀**(node 的 ffmpeg 子进程会被带走)。
|
||||
6. **Windows 无 `head`/`tail`**,用 `findstr` 或 python。
|
||||
7. **改完源码要重启 node**(磁盘 .py/.js 改了,运行中的进程不生效)。
|
||||
8. **Python 跑 Windows 输出**:`chcp 65001 && set PYTHONIOENCODING=utf-8`。
|
||||
9. **`node -X utf8` 是错的**(那是 Python 的选项),node 直接 `node` 即可。
|
||||
|
||||
---
|
||||
|
||||
## 11. 当前运行态
|
||||
|
||||
- 后台服务:`svc005`(PID 15308 起),日志 `service_log svc005`
|
||||
- DB:`data.db`(5 首歌曲,封面已本地化)
|
||||
- 缓存:`hls_cache/` 5 个目录,约 28MB
|
||||
- Git 工作区:干净(最新 `a4745db`)
|
||||
|
||||
---
|
||||
|
||||
## 12. 下一步建议(按优先级)
|
||||
|
||||
1. **问清用户的"问题"具体是什么** ← 最优先
|
||||
2. 补前端"新建/删除流"UI(若需求确认)
|
||||
3. 浏览器手动过一遍所有交互,修 Console 报错
|
||||
4. 清理死代码(§9.4)
|
||||
5. 正式化:设置固定 `PANEL_PASSWORD`,写 systemd/服务化启动
|
||||
6. 补多路流并发压力测试
|
||||
Reference in new issue
Block a user