每日一言修复方案
发行方式已被取代(2026-11-07):本文描述的「读者读取时才产生当天那期」已由
reports/product-decisions-2026-11-07.md的 P03 取代 —— 改为后台每日独立发行、 只补当天、停机缺期不补。当前实现见docs/architecture.md§6.22 的 「发行与阅读分离」;本文其余修复(往期、共鸣、收藏)仍有效。
状态:已实施(2026-10-29);往期回看、累计一次共鸣与旧记录保留均已上线,参与门槛与部分 细则此后调整过。当前行为以
docs/architecture.md§6.22 与服务实现为准,经过见 Git 历史。2026-11 校订(R3-705):①「同一句 90 天后再次入选」是本修复方案当时的口径,现行 候选池对社区稿只选登一次(
daily-quote-service.ts的firstSelectedDay: null+editions: { none: {} });②不变量 4「社区稿由付款者付 1 条并全额交给投稿人」现行是 三元式 ——kind === 'community' && canReceive(recipient) ? QUOTE_RESONANCE_FISH : 0, 收款人失格 / 禁言时双方都不动鱼干。
1. 当前事实与入口
- 页面:今日
/quotes、往期一言/quotes/history、一言集/quotes/saved、投稿/quotes/submissions;旧地址/checkin/quotes、/favorite/quotes由legacyRedirect永久跳转。 - 接口:
GET /api/daily-quotes/today、GET /api/daily-quotes/history?page=1、POST /api/daily-quotes/[id]/resonance、POST /api/daily-quotes/[id]/favorite、POST /api/daily-quotes/submissions。
⚠️ 2026-11-07 产品决定(P02)已取消本文档中的「博客前置」与「今日读取须当天签到」两项门槛: 现在未归档 core+ 即可阅读/参与/投稿/收藏,今日直接可读。下文相关描述为历史记录,按新规则执行。
- 参与门槛对所有入口一致:未归档 core+;不满足时接口 403(角色/归档),写操作禁言另拒。 (历史:曾要求名下有未软删博客,并于第二天起要求当天签到;两者均已取消。)
- 往期数据来自
daily_quote_editions,按day < todayStr()倒序,每页 20 期 (QUOTE_HISTORY_PAGE_SIZE),纯读取、不补抽、不创建 edition;分页复用SitePager。
3. 历史列表与权限边界
3.1 历史列什么
- 只列真实存在且早于今天的选用记录;不公开候选池,不列已通过但未入选、待审与被拒稿件。
- 缺日不补抽;同一句 90 天后再次入选保留两个日期条目,互动状态与共鸣人数按 quoteId 共用。
- 已下架或作者已归档的内容保留日期与不可用占位,不显示原文、出处与身份,不允许新增共鸣。
- 历史查询是纯读取,不调用
selectEdition或getTodayQuote,不会为今天或过去补抽;往期页 排除今天,不能当绕过签到的今日读取入口。
3.2 今日与历史的边界(按当前实现)
| 身份 / 状态 | 往期、一言集 | 今日 |
|---|---|---|
| core+(无论是否发过博客) | 可看、可共鸣、可收藏,不要求当天签到;只能共鸣早于今天的已发布内容 | 直接可看(P02);状态由发行记录决定(P03) |
| 禁言中的 core+ | 只读(不能新增共鸣/收藏) | 只读 |
| 未登录 / 普通用户 | 登录引导与 core+ 限制,页面与接口各自校验 | 同左 |
| 禁言中的 core+ | 可看;投稿、共鸣与收藏变更由服务端拒绝 | 同左 |
4. 共鸣累计一次与旧数据处理
4.1 不变量
(quoteId, userId)一生最多一次有效共鸣;日期只记录发生时间,不参与权限与去重。- 换日、刷新、重启、取消 / 重新收藏、再次入选都不恢复共鸣资格。
- 已共鸣不提供取消;重复请求幂等返回,不增加人数、不产生鱼干流水。
- 社区稿件由付款者付 1 条鱼干并全额交给投稿人,经典免费;不能给自己的投稿共鸣。
- 资格行、明细行与两侧记账在同一事务;余额不足或作者入账失败时全部回滚。
- 收藏独立:没有共鸣也能收藏,取消收藏不撤销也不重置共鸣。
4.2 数据结构
daily_quote_resonance_once(quote_id + user_id 联合主键,first_resonated_at)是累计
资格与人数的唯一依据,迁移 37_daily_quote_resonance_once 用 MIN(created_at) 回填;
daily_quote_resonances 保留每次发生的明细,只服务历史审计。旧行、旧流水与余额都不改写。
4.3 存量重复的产品呈现
同一人过去三天各共鸣一次时:明细与流水都保留,累计表只有一行,界面按人数计 1 而不是 3, 因此部分内容上线后显示的共鸣数可能下降;文案统一为「已共鸣」「每人每句限一次」。
存量重复组合数、涉及付费次数与金额仍未做只读统计;是否补偿旧重复投喂要先有统计再定
(登记见 docs/plan/reactive-md-migration-plan.md §12 第 6 项)。不删除历史流水。
8. 发布边界
- 累计表、代码与回填作为一次切换;不能出现旧版本仍按日写入而累计表不同步的窗口。
- 回退只回退 UI 与非写路径,保留累计写入保护;绝不运行旧的按日共鸣代码(会重新放开扣款)。
- 旧余额与流水不依赖本次迁移改写;生产部署与迁移是否执行不由本文记录。
9. 验证记录
- 相关测试(服务 / 路由 / 经典目录 / 迁移):6 个文件、35 项通过(2026-10-29)。
- 迁移
37_daily_quote_resonance_once用真实含跨日重复的 SQLite 夹具验证回填、重复执行与 联合主键(历史记录:原tests/unit/daily-quote-resonance-migration.test.ts;Round4 三个 daily-quote 迁移 spec 已并成tests/unit/daily-quote-migrations.test.ts)。 - 浏览器:
tests/e2e/daily-quote.spec.ts桌面 11 项、手机 11 项通过;typecheck 与 build 通过。 - 全量 vitest 267 文件 / 5,305 用例,剩余 21 条红来自当时工作区的
host-components.tsWIP, 与本次改动无关(docs/known-red.md§8 同一例)。