9.1 KiB
9.1 KiB
交接文档 · 音乐在线电台
跨会话交接。新会话先读此文件再动手。最后更新: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. 关键设计决策(勿轻易推翻)
- SQLite better-sqlite3 + WAL:单进程同步 API 最省事,WAL 解读写互斥
- 不迁 v1 JSON 数据(旧 heat/metadata/playlist.json 已删)
- 鉴权整站 Cookie 会话;HLS 流不锁(播放器难带 cookie)
- 封面/资源全本地化:
public/covers/、public/vendor/;系统字体 - songs.file 存文件名非绝对路径,可移植
- 导入先落 tmp 再 rename,避免 chokidar 扫到半截
- 双密码角色:用户 = 管理员 − 上传 − 设置页;后端
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. 工具层踩坑记录
- XML 实体转义吃
&":写 JS/CSS 用String.fromCharCode(38)或字符码构造 - ⚠️ 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)}) - replace_lines 多段连锁崩:同一文件多段改,某段行号错会留残留。逐条改,改前 node 查号、改后
node --check - 文件被改乱:别手修,
git checkout <commit> -- <file>恢复 - 后台服务用 action background="true"(勿
start /b) - service_stop 连子进程一起杀(含 node 的 ffmpeg 子进程)
- 改完源码要重启 node 才生效
- Windows 无 head/tail,用 findstr 或 node
- cmd 下 curl:
%{http_code}要写%%{http_code};中文输出 GBK 乱码(功能正常) - git 提交中文:powershell 写无 BOM UTF-8 文件 +
git -c i18n.commitEncoding=utf-8 commit -F 文件 - 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. 下一步可选
- 浏览器手动过一遍所有交互,修 Console 报错 ← 最优先
- 播放延迟调节(HLS_TIME 热更新,老板曾暂缓)
- 密码设置 UI(现走环境变量)
- 缓存生成进度条、搜索分页/加载更多
- mini player 音量控制、播放历史/最热排序(heat 字段已有)
- 正式化:固定强密码、服务化启动
12. 当前运行态
- 后台服务:
svc003(http://127.0.0.1:3000) - DB:
data.db(6 首歌曲,封面已本地化) - 缓存:
hls_cache/约 34MB - Git:
2fc2548干净