v9 迁移内额外执行一条数据清理语句:DELETE FROM lexgo_book_attachments WHERE kind = 'audio'(幂等、可重放),lexgo_playback_positions 表整体保留但停止写入(不删表,避免破坏"只加不删"的迁移习惯;如需彻底清理留待后续单独评估)。
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
来源与目标
用户在当前会话提出:"可以导入音频和点击播放音频,音频和文本不需要同步;然后给书本配上封面"。随后确认先建工单,再按 #4 → #5 的顺序推进;本次指令为"建工单"。建单时间:2026-09-10T22:00:11+08:00。
为本人已有书籍附加可选音频和封面,阅读时独立播放音频,书库更容易识别书籍。本单是 F01–F12 之外明确新增的 MVP 附件能力,关联总览 #16;不把原型、建单或既有 #3 小样视为本功能已实现。
已确认首版范围
非目标
多音轨、分章节音轨、文本时间戳同步、字幕对齐、逐句跟读、音频转文字、TTS、自动搜索封面、在线音频地址抓取、公开分享和跨用户附件共享不在本单范围。
验收标准
依赖、顺序与状态
类型:功能增补。状态:待验收(2026-09-15,提交 ab23ff6,PR #36;契约见评论 8116,实施证据见评论 8124)。
前置:#5 基础导入与书籍阅读完成并验收(已完成)。
#10 是书籍编辑/删除的协同任务,不要求先完成全部 #10 才能设计本单;实施前核对其状态,复用已有编辑入口或提供最小附件编辑入口,避免重复建设。#15 负责最后整站备份恢复验收。
范围变更审议中(2026-09-15,未确认,不影响上述已交付范围):用户提出多篇网络文章合并成一本书多章、音频与新增插图改为章级的场景;第二次范围变更草案见评论 8129,等待用户确认后再生效并转回"进行中"。在用户明确确认 8129 之前,本工单的有效范围仍是上面"已确认首版范围"(书级音频+封面),与已交付代码一致。
设计证据与实现前检查
复用已验收 Quant-UX v1 的学习端风格;新增书籍附件编辑、书库封面与阅读播放器的桌面/手机关键状态,已按评论 8116 的标注线框(S1–S4)确认并实施。
技术设计见评论 8116:附件元数据、私人文件存储与授权传输(含 Range)、播放进度保存频率、文件替换一致性与清理恢复。
估算、风险与回退
初步规划 4~6 人日,含原型调整、前后端、附件权限、测试与文档;是 Agent 规划参考,不代表实际工时或日期承诺。主要风险是音频体积、浏览器格式兼容、鉴权媒体请求和附件清理一致性。
新增元数据先在隔离测试环境迁移/回退,保留现有书籍与原文;附件写入失败可重试,删除操作须遵循授权与清理契约,禁止以测试名义删除用户真实文件。
文档与证据
实施证据见评论 8124:Wiki 页面 revision、测试结果、未验证内容与回退方式。#15 部署/恢复文档已含附件存储范围。
Gitea MCP 指向其他站点,沿用目标 git.ilapage.cn API 回退;凭据仅进入进程。项目使用现有正文状态流转,不新增其他技能的标签体系或本地工单目录。
#21 设计证据(形式 b:标注线框 + 状态说明)与契约草案(2026-09-15)
用户 2026-09-15 确认「做 #21,按建议的默认 + 用 (b) 设计证据」。本评论先交付可审阅的设计证据与实施契约;按 #21 正文的门禁,新增交互需用户确认后编码,因此工单在本评论后置为「待确认」,用户确认即开始实施。
一、设计证据(标注线框)
复用现有规范与布局,不新增配色、不新增页面级导航。
S1 书库卡片(LibraryView,桌面/手机同构)
手机:单列、封面随宽度自适应,不产生横向滚动。
S2 书籍页「音频与封面」区块(BookView,位于现有
.page-actions下方;不用对话框)S3 阅读页播放器条(ReaderView;仅当该书有音频时出现)
position: sticky; top: 0(手机同样 sticky)。S4 状态与持久化规则
二、实施契约
D1 数据(schema v8,新表,可重放 DDL)
lexgo_book_attachments:book_id+kind('audio'|'cover')唯一,owner_id、mime、byte_size、sha256、bytes MEDIUMBLOB、时间戳;外键级联到书籍与账号。一本书最多一段音频、一张封面。lexgo_playback_positions:主键(owner_id, book_id),position_seconds、updated_at;外键级联到账号与书籍。ALTER TABLE」的既定做法,迁移与回退测试照 #13。D2 接口
POST /api/v1/books/:id/cover、POST /api/v1/books/:id/audioDELETE /api/v1/books/:id/cover、.../audioGET /api/v1/books/:id/cover、.../audiohttp.ServeContent自动支持 Range(206)、ETag/Last-Modified与条件请求PUT /api/v1/books/:id/playback{positionSeconds},upsert,仅本人GET /api/v1/books、GET /api/v1/books/:idD3 校验:音频 ≤ 20 MiB、封面 ≤ 2 MiB、封面像素 ≤ 4096×4096;按文件头 magic bytes 判定类型(MP3:ID3 或 0xFFEx 帧同步;JPG/PNG/WebP 各自签名),扩展名不参与判定;超限 413,类型/内容非法 400 且消息可读;不用文件名做路径。
D4 权限与清理:附件属于书籍所有者,任何跨账号读写返回 404;删除书籍级联删除附件与所有账号的该书进度;替换失败不写库(先校验后事务 upsert),旧附件保留。
D5 播放进度:只做上界校验(0 ≤ 秒 ≤ 24h)与 upsert;并发以最后一次写入为准;不解析音频时长(时长由浏览器提供,仅用于界面)。
D6 界面:实现 S1–S4;桌面与 390×844 触摸视口都要可用,不遮挡正文与划词面板。
D7 #15 衔接:附件存 MySQL,因此现有 dump 自动包含附件与进度;完成后重跑 #15 的恢复演练并新增附件断言(附件行数、字节 sha256 一致,恢复后仍可播放/读取),部署页与运维页补「备份含附件及其体积影响」。
D8 范围外:多音轨/分章音轨、字幕与文本同步、TTS、转码、自动搜索封面、公开分享、跨用户附件共享、时长解析。首版音频只收 MP3(最终支持以桌面与手机浏览器实测为准,不承诺任意格式)。
D9 测试:Go 单测(magic bytes、上限、Range 头、进度 upsert/幂等/重置、级联)、MySQL 集成(上传→读取→Range 206→条件请求→替换→移除→删除书籍级联)、前端单测与 e2e(mock + 390×844)、真实链路检查、迁移 v7→v8 与「回退标记后重新升级」。
D10 文档:Architecture、Business-Rules、Local-Development、Product-Requirements、Home,以及 Deployment-and-Operations 的备份范围与容量提示。
D11 回退:schema v8 只新增两张表;回退二进制前把
lexgo_schema.version写回 7 即可,不影响既有数据。D12 已知限制(写入文档):音频不做转码,浏览器兼容性以实测为准;把音频放进 MySQL 适合少量邀请用户的实例,不适合大规模音频库(单文件上限 20 MiB 已写明)。
三、请用户确认
#21 实施启动(2026-09-15)
用户 2026-09-15 确认「可以」,并授权一并修掉
e2e/phrase.spec.ts的偶发失败。设计证据按评论 8116 冻结(线框 S1–S4 与状态规则),契约 D1–D12 不变。分支feat/21-book-attachments从 maindde2ac1创建。实施顺序:
e2e/phrase.spec.ts:63的偶发失败(#11 遗留,与 #21 无关但会干扰后续全量回归);lexgo_book_attachments、lexgo_playback_positions)、上传/替换/移除、带 Range 的受鉴权读取、播放位置上报表、书籍列表与详情返回封面版本与音频元数据;工单转「进行中」,完成后停在待用户验收。
#21 实施完成,待用户验收(2026-09-15)
用户 2026-09-15 确认设计证据(评论 8116 的线框 S1–S4 与状态规则)并授权一并修掉
e2e/phrase.spec.ts的偶发失败。分支feat/21-book-attachments从 maindde2ac1创建,提交ab23ff6已推送;PR #36 未合并,工单停在待用户验收。实现与差异
ALTER TABLE):lexgo_book_attachments(主键book_id+kind,bytes MEDIUMBLOB,外键级联到书籍与账号)、lexgo_playback_positions(主键owner_id+book_id,级联到账号与书籍)。server/app/lexgo/attachment.go:按文件头 magic bytes 判定类型(MP3 接受 ID3 或帧同步;JPG/PNG/WebP 各自签名,WebP 解析 VP8X/VP8/VP8L 头取维度);音频 ≤20 MiB、封面 ≤2 MiB 且 ≤4096×4096;先校验后 upsert,被拒绝的上传不动旧文件;替换或移除音频即清空位置。读取端点通过新增的binaryResponse交给http.ServeContent,因此支持 Range/206、416、If-Modified-Since 与 ETag/304;书籍列表新增coverVersion/hasAudio,书籍详情与章节响应带attachments。session.requestBlob带凭据取字节并转对象 URL(令牌不进 URL),库列表批量预取封面、阅读器按需取音频;AudioPlayer.vue(播放/暂停、拖动、0.75–1.5 倍速、错误重试、每 5 秒与暂停/离开上报位置);书库卡片封面(重复链接对无障碍隐藏,标题链接仍是唯一入口)、书籍页「音频与封面」区块、阅读页 sticky 播放器条。e2e/phrase.spec.ts的偶发失败:重试时重新读取词坐标(面板打开会改变布局)、松开前留 80 ms 让浏览器扩展选区、重试条件是断言的期望词数而不是「面板出现了」、重试前关闭部分选中的面板。验证
go vet/gofmt -lLEXGO_TEST_DB_NAME=lexgo_test_issue13 python scripts/server.py test-integrationvitest/vue-tsc/build/playwright testcheck --strict、sync --check通过OCTET_LENGTH与 sha256 一致、ETag/304、Range得 206 与正确切片、不可满足 Range 得 416、越权与匿名被拒、SVG/文本改名被拒、21 MiB 得 413 且旧文件保留、位置读写与负值拒绝、替换后位置归零且仍一行、移除与删书级联清空lexgo verify --manifest通过(含新增的「附件都指向存在的书」等检查)→ 15 张表逐表内容校验和与源库一致 → 用恢复库起实例:同一账号可登录、书籍仍报告两个附件、位置仍是原值、音频与封面按字节相同地返回、Range 仍得 206、越权仍 404迁移:v7 → v8 只新增两张表,开发库逐表业务计数无变化,schema 记到 8;本机 lexgo-api 已用新二进制重启,回退二进制
.local/lexgo-pre-issue21.exe。未验证(如实记录)
文档
3b785a11352776e36656da59ef9928a2cbb4269434a1994dae20957a68f2bd60e3a7dd97530cffacba5e3acc746432e55c186fd3a1fdf3c158fd121b437d154868c23dd5b39e525d6c45e9ff13364d47e1e363e39ea02c26fab7f96c696ee20e60dcec60dacdad8a9385b2fde153a0dd1fe2579b57a810fd(备份范围含附件与体积提示)sync --check通过,6 个变更页逐字节正文比对一致回退
schema v8 只新增两张表:停止 lexgo-api、把
lexgo_schema.version写回 7、恢复.local/lexgo-pre-issue21.exe后重启即可;既有书籍与学习数据不受影响。Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。
证据文件(本机,不入仓库):
.local/issue21-api-evidence.json(41 项)、.local/issue21-restore-evidence.json(22 项)、fixture 的真实 MP3 与 PNG(.local/evidence/issue21-fixture.*)。MVP 增补:书籍音频与封面附件(音频不与文本同步)to MVP 增补:书籍封面、章节音频与章节插图(音频不与文本同步)范围变更记录(2026-09-15)
触发场景
用户提出实际使用场景:"有个场景,我收集了多篇网络文章,每篇有自己的音频,要怎样来实现"。经讨论确认组织方式为"多篇文章合并成一本书的多章",因此音频挂载单位需要从建单时确认的"每本书一个"改为"每个章节一个",播放进度记忆粒度同步从"账号+书"改为"账号+章节"。
讨论中进一步确认新增插图需求,用户原话:"书籍封面只有一个,我想给每章节加个图片,当插图或章节图片,增加阅读氛围和兴趣"。确认为:一章最多一张插图,固定展示在该章正文开头,可复用为书籍详情页章节列表的缩略图;不支持正文中间图文混排、不支持多图。
封面维持书级不变——用户明确认可"封面仍然按书"的建议。
变更内容
影响范围
Gitea MCP 仍指向其他站点,沿用目标 git.ilapage.cn API 回退;凭据仅从既有安全配置读入进程。
#21 范围变更(第二次,草案,待确认)——2026-09-15
背景
PR #36(提交
ab23ff6)已按评论 8116 冻结的 D1~D12 完成书级音频与封面,86 项集成用例、真实链路 41 项、#15 恢复演练 22 项全部通过,目前停在待验收,尚未验收关闭。用户随后提出实际场景(多篇网络文章合并成一本书的多章,每篇/每章各有自己的音频,并新增章节插图需求),与已交付的书级方案不一致。用户已确认:在 PR #36 已交付的代码基础上继续改,不推倒重做,本评论是这次改动的完整契约草案,供确认后再实施。
一、复用与保留(不改动)
lexgo_book_attachments的kind='cover'、书籍读写接口、coverVersion缓存失效机制原样保留。http.ServeContent的 Range/ETag/条件请求包装、上传校验流程(先校验后事务写入、失败保留旧附件)、AudioPlayer.vue、session.requestBlob取字节转对象 URL 的机制,全部原样复用到章级。二、退役(书级音频整体下线)
lexgo_book_attachments的kind='audio'不再接受新写入;POST/DELETE/GET /api/v1/books/:id/audio与PUT /api/v1/books/:id/playback三个接口连同lexgo_playback_positions表一并退役。三、实施契约(D1~D12,覆盖第一版对应条目)
D1 数据(schema v9,新表,可重放 DDL,不用 ALTER TABLE)
lexgo_chapter_attachments:chapter_id+kind('audio'|'illustration')唯一,owner_id、mime、byte_size、sha256、bytes MEDIUMBLOB、时间戳;外键级联到lexgo_chapters(id)与账号。一章最多一段音频、一张插图。lexgo_chapter_playback_positions:主键(owner_id, chapter_id),position_seconds、updated_at;外键级联到账号与章节。DELETE FROM lexgo_book_attachments WHERE kind = 'audio'(幂等、可重放),lexgo_playback_positions表整体保留但停止写入(不删表,避免破坏"只加不删"的迁移习惯;如需彻底清理留待后续单独评估)。D2 接口
POST /api/v1/chapters/:id/audio、.../illustrationDELETE /api/v1/chapters/:id/audio、.../illustrationGET /api/v1/chapters/:id/audio、.../illustrationhttp.ServeContent提供 Range/206、ETag/304PUT /api/v1/chapters/:id/playback{positionSeconds},upsert,仅本人GET /api/v1/books/:id(章节列表)POST/DELETE/GET /api/v1/books/:id/audioPUT /api/v1/books/:id/playbackD3 校验:音频规则不变(≤20 MiB、MP3 magic bytes);插图与封面共用同一套校验(JPG/PNG/WebP、≤2 MiB、≤4096×4096、magic bytes 判定,不看扩展名)。
D4 权限与清理:附件属于章节所有者,跨账号读写 404;删除章节(复用 #10 已确认的事务级联)连带删除该章附件与播放位置;删除书籍通过章节级联连带清空。
D5 播放进度:粒度改为账号+章节;规则不变(0~24h 上界、最后写入为准、不解析时长)。
D6 界面(改写 S2,新增 S2b,S1/S3/S4 结构不变、归属改为章级)
D7 #15 衔接:#15 恢复演练需要第三次重跑(第一次覆盖数据库,第二次加书级附件断言,这次改成章级附件断言:附件行数、按章节归属、sha256 一致、恢复后仍可按章播放/显示插图)。
D8 范围外:与第一版一致,另加"文章缩略图墙"式独立浏览界面明确排除(已写入工单正文非目标)。
D9 测试:在 v8 已有用例基础上,新增插图类型/尺寸校验、章节删除级联清空章级附件与位置(复用 #10 的删除测试夹具)、v8→v9 迁移与"回退标记后重新升级"、书级音频接口确认已下线(404 或路由不存在)、书级历史音频行被迁移清空。
D10 文档:Architecture、Business-Rules、Local-Development、Product-Requirements、Home、Deployment-and-Operations 均需修正为章级口径,删除书级音频的描述。
D11 回退:v9 只新增两张表:回退到 v8 二进制会重新看到(已清空的)书级音频接口,但因为迁移已经清空了
kind='audio'的历史行,回退到 v8 无法找回这批测试期数据——鉴于目前只有测试数据、没有真实用户,这个代价可以接受,工单会明确记录这一点。D12 已知限制(写入文档):本单是 #21 的第二次范围变更,第一次交付的书级音频功能整体作废,相关实现与测试投入不再产生用户可见价值;后续如需再评估附件挂载粒度,应先确认真实使用场景,避免第三次返工。
三、请用户确认
MVP 增补:书籍封面、章节音频与章节插图(音频不与文本同步)to MVP 增补:书籍音频与封面附件(音频不与文本同步)更正说明(2026-09-15)
我在评论 8128 里把工单正文改成了"章级音频+章级插图",但那次修改基于的信息不完整——当时不知道 PR #36 已经按评论 8116 的书级方案实施完成(评论 8124),导致正文描述的范围和已交付代码不一致。
现已把工单标题与正文改回评论 8128 之前的书级版本,与当前已交付、停在待验收的代码状态保持一致。评论 8128 记录的讨论过程本身不撤销(保留作为历史记录),但其"已生效"的表述作废。
第二次范围变更(章级音频+插图)的完整草案见评论 8129,目前是待确认状态,尚未生效。用户确认后,我会再次更新正文并把工单转回"进行中"。
Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。
#21 验收通过(2026-09-15)
用户 2026-09-15 回复「#21 验收通过」,随后确认「A,并新开章级工单」:接受本次交付的书级封面与音频,并把章级音频/章节插图的变更作为新工单承接。
ab23ff6。e2e/phrase.spec.ts)、管理端 31 项、治理 65 项;真实 API+MySQL 41 项(真实 PNG 与 ffmpeg 生成的三秒 MP3,逐字节比对、Range 206/416、ETag 304、越权与校验拒绝、替换归零、级联清理);#15 恢复演练扩展 22 项,恢复后附件按字节相同地返回、Range 仍可用。08472d2c98a5b4ac1b0354b09c6319ac74d321f7、Business-Rules34a1994dae20957a68f2bd60e3a7dd97530cffac、Local-Developmentba5e3acc746432e55c186fd3a1fdf3c158fd121b、Product-Requirements2d6563574e807c9a167b90b7c772ee8b9b81e76d、Homeeef77f39332c2bef01d5e9788a2c6c89796b2a1a、Deployment-and-Operationsdacdad8a9385b2fde153a0dd1fe2579b57a810fd。lexgo_schema.version=7并恢复.local/lexgo-pre-issue21.exe即可。单元工单关闭;#16 的 #21 复选框同步勾选。