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

9.1 KiB
Raw Blame History

交接文档 · 音乐在线电台

跨会话交接。新会话先读此文件再动手。最后更新: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 /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)

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. 测试方法

服务起来后:

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 素材)

素材生成:

"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 查行号:
    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 干净