Files
StarlightRadio/HANDOFF.md
T

11 KiB
Raw Blame History

交接文档 · 音乐在线电台 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
  • 启动:
    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)

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 / 或新起)跑:

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. 已完成清单

  • SQLite 替换 JSON(5 表 + WAL)
  • 级联删除 /api/music/delete + 批量
  • 多路流 LiveStream 类 + 每路独立播放列表
  • Cookie 会话鉴权 + 登录页 + 鉴权绕过漏洞修复
  • 本地上传导入(multer)+ 元数据自动匹配
  • 封面本地化 + vendor 本地化 + 系统字体
  • 日志系统(分级/落盘/面板可查)
  • API 统一返回结构
  • 优雅退出补 FFmpeg 子进程 kill
  • 模块化拆分(1233 行单文件 → src/ 12 模块)
  • 前端 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. 补多路流并发压力测试