# 交接文档 · 音乐在线电台 > 跨会话交接。**新会话先读此文件再动手**。最后更新: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 -- ` 恢复 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` 干净