跳转到内容

每日日语 · 内容系统实施规格文档

每日日语 · 内容系统实施规格文档

Section titled “每日日语 · 内容系统实施规格文档”

[!IMPORTANT] 文档状态说明(2026-03-28)

这份文档保留了原始实施规格正文,以及后续多轮自查、修复、补充和扩展痕迹,作为历史留档继续保留

为避免当前执行继续被过重文档干扰,Yomiya 文档体系已经开始拆分。当前执行请优先阅读以下文档:

  1. yomiya-phase1-execution-spec.md:Phase 1 当前执行收口版
  2. yomiya-canonical-naming.md:命名冻结表
  3. yomiya-phase1-scope-boundary.md:Phase 1 范围边界清单
  4. yomiya-implementation-spec-core.md:当前有效实施规格主文档
  5. yomiya-implementation-review-log.md:历史审查与修复记录
  6. yomiya-implementation-open-questions.md:尚未定稿的开放问题
  7. yomiya-implementation-future-phases.md:后续阶段能力规划

阅读建议:

  • 要开工,看 execution-spec + naming + scope-boundary + implementation-spec-core
  • 要查历史问题,看 implementation-review-log
  • 要看未决事项,看 implementation-open-questions
  • 要看后续路线,看 implementation-future-phases

处理原则: 本文档主体内容不做大规模删改,继续保留原始留档价值;新增文档负责承接当前执行、历史问题和未来规划。

性质:可执行实施规格,所有结论为定量定死的数字,不存在”大约”或”至少” 前置文档yomiya-content-strategy.md(目标形态)/ yomiya-engineering-gap-analysis.md(工程差距) 本文档定位:将策划结论转化为前端原型、工程排期、运营执行的直接输入 创建:2026-03-27 | 最后更新:2026-03-27(吸收 cc 15条反馈,全量更新) 参与:cc(产品负责人)× Captain(OpenClaw 主控 AI)


  1. 首页模块精确规格
  2. 首批合集规格(6个)
  3. 内容入库判断规则
  4. LLM 打标 Prompt 规格
  5. 定期智能分析任务规格(冷启动期版本)
  6. 内容采集搜索清单
  7. 用户痛点与内容模块映射
  8. 合集数据结构设计
  9. 今日打卡 / 学习进度定义
  10. 内容标题处理规则
  11. 封面图获取与存储策略
  12. 待解决的模糊点清单

┌──────────────────────────────────┐
│ ① Banner 轮播区 │ 高度:200px,轮播数:3张,切换间隔:5秒
├──────────────────────────────────┤
│ ② 今日学习区(原"今日打卡") │ 固定展示,每日刷新,基于今日学习时长判定
├──────────────────────────────────┤
│ ③ 热门推荐(合集区) │ 横向滚动,展示8个合集卡片
├──────────────────────────────────┤
│ ④ 每日资讯 Tab 区 │ 6个Tab,每Tab默认展示6条
├──────────────────────────────────┤
│ ⑤ JLPT 专区入口 │ 5个级别按钮(N5/N4/N3/N2/N1)
├──────────────────────────────────┤
│ ⑥ 轻松入门区 │ 固定展示4条入门内容(常青)
├──────────────────────────────────┤
│ ⑦ 名师专栏 & 播客系列 │ 展示2个播客系列入口
├──────────────────────────────────┤
│ ⑧ 热门专题(运营展台) │ 展示1个当前专题
└──────────────────────────────────┘
参数
轮播图数量3 张(固定,后台配置)
图片比例16:9,1080×607px
切换间隔5 秒
运营更新频率每两周更换一次
内容用途推广当期重点合集 / 限时活动 / JLPT 倒计时

定义修正:今日打卡不以”完成某条内容”为判定,而以”今日累计学习时长”为判定。

参数
打卡判定标准当日在 App 内累计有效使用时长 ≥ 15 分钟(有效使用 = 视频播放 / 音频播放 / 文章阅读,不含挂后台)
展示内容数1 条”今日推荐”内容(每日刷新)
内容类型优先级视频 > 音频 > 图文
Streak 递增当日累计有效时长达到 15 分钟
Streak 中断恢复付费用户每 30 天可购买 1 次复活
展示字段封面图 + 标题 + 时长 + 难度标签 + 今日学习进度(已学 X 分钟 / 目标 15 分钟)
游客状态展示通用版今日推荐,无 Streak 功能
登录用户同上 + Streak 天数 + 个性化推荐(个性化推荐在冷启动期不启用,预留字段)

今日推荐选取逻辑(冷启动期):从当日新入库内容中,按以下优先级选取 1 条:

  1. type = video + level = 3(N3) + 今日入库
  2. type = video + 任意 level + 今日入库
  3. type = article + level = 3(N3) + 今日入库

1.4 模块③:热门推荐(合集区)

Section titled “1.4 模块③:热门推荐(合集区)”
参数
展示合集数量8 个(固定)
其中视频合集5 个
其中图文合集3 个
卡片尺寸160×240px(纵向卡片)
卡片展示字段封面图 + 合集名(≤14字)+ 集数 + 难度标签(N5–N1)
滑动方向横向滑动

合集启动硬规则

合集类型最低条目数低于此数时处理
视频合集8 条不创建合集,内容加入”待建合集暂存区”
图文日更合集20 条(历史存量)不上线该合集入口
图文常青合集12 条不上线
播客系列6 期不展示,继续储备

过渡方案(collections 表未上线前):后台 featured_items 配置,人工指定 8 条精选内容,以卡片列表替代合集区。


Tab 分类逻辑修正:不是资讯”属于哪个 Tab”,而是”这条内容的 primary topic_tag 对应哪个 Tab”。

topic_tag → Tab 映射表(完整,无遗漏)

topic_tag对应 Tab
社会观察社会
历史人文社会
自然与科学社会
J-pop与音乐娱乐
日剧娱乐
综艺娱乐
日本文化文化
节日与习俗文化
饮食文化文化
城市与生活文化
动漫动漫
科技趋势科技
旅行旅行
职场商务文化(暂归文化,未来独立)
日常场景文化(暂归文化,未来独立)

当 primary topic_tag 无法映射时:归入”社会” Tab(默认兜底)。

Tab 上线条件(定死)

Tab历史存量要求日更要求当前状态
社会60 条2 条/日✅ 立即上线
娱乐60 条2 条/日✅ 立即上线
文化40 条2 条/日❌ 待 NHK World 接入
科技40 条2 条/日❌ 待 NHK World 接入
动漫30 条1 条/日❌ 待动漫频道接入
旅行30 条1 条/日❌ 待旅行类内容采集

不满足条件的 Tab 不展示,首页 Tab 区初期只展示 2 个(社会 + 娱乐)。


参数
按钮数量5 个(N5 / N4 / N3 / N2 / N1)
各级别上线条件该级别内容存量 ≥ 30 条
列表页排序发布时间倒序(最新在前)
列表每次加载10 条
展示字段封面 + 标题 + 时长/字数 + 来源 + 发布时间

当前各级别存量全部满足(均 > 200 条)。立即可上线。


参数
展示条数4 条(常青,不日更)
入选标准level = 1(N5) + duration_sec ≤ 300 或 word_count ≤ 300
更新频率每 30 天人工替换 1 次

参数
上线条件同时有 2 个系列,各储备 ≥ 6 期
封面来源RSS 原始封面图,直接引用(不重新设计)
节目简介优先使用 RSS description;若 RSS description 与用户价值匹配度低(AI 判断相关度 < 0.5),则 AI 重新生成中文简介(≤60字)
更新频率每周 1 期(固定承诺,中断则隐藏该系列入口)

参数
同时展示专题数1 个
每个专题包含条目10 条(固定,低于 10 条不上线)
专题存续时间14 天
内容来源从现有 news 表按 topic_tags 筛选 + 专题期间专项采集入库两种方式并用

字段
类型图文日更合集(collection_type = rolling_feed)
封面固定一张 NHK 品牌封面图(深蓝底色 + NHK Logo),不随内容变化
内容来源自动:每日新入库的 channel_id = NHK 内容自动追加
合集条目上限无上限(持续追加,保留全部历史)
前台展示条数每次加载 10 条,发布时间倒序
难度标签N3–N4
付费墙无(全部免费)
完结状态永不完结(is_finished = false)
字段
类型图文日更合集(rolling_feed)
封面固定一张娱乐风格封面图
内容来源自动:channel_id = YahooNewsEntertainment 每日自动追加
合集条目上限无上限
前台展示条数每次加载 10 条,发布时间倒序
难度标签N2–N3
付费墙
完结状态永不完结
字段
类型视频合集(curated,人工筛选)
封面由运营人工上传(日本传统元素,16:9)
内容来源人工筛选 + NHK World 文化类视频自动推送到待选池
合集内排序LLM 评定的优先级分数(priority_score)降序排列
最低开合集条目数8 条
付费墙部分内容会员专属(is_membership = true)
完结状态永不完结(持续更新)
YouTube 采集关键词”Japanese culture explained” / “日本文化 解説”
字段
类型视频合集(curated,场景结构化)
封面运营上传(日本街景/机场,16:9)
场景覆盖(固定结构)机场2条 / 酒店2条 / 餐厅2条 / 交通1条 / 购物1条 = 共 8 条
合集内排序场景顺序(机场→酒店→交通→餐厅→购物),LLM 生成 priority_score 辅助
最低开合集条目数8 条(覆盖以上 5 个场景)
付费墙前 4 条免费,后 4 条会员专属
完结状态永不完结(持续补充场景)
字段
类型视频合集(curated)
封面运营上传(动漫风格插图)
内容方向官方动漫 PV + 第 1 话试看 + 经典台词解析
合集内排序priority_score 降序
最低开合集条目数8 条
付费墙前 3 条免费,其余会员专属
完结状态永不完结
字段
类型混合合集(视频 + 图文,curated)
封面运营上传(简洁学习风,含”N3”大字)
内容构成视频 10 条(词汇5 + 语法5) + 图文 10 条(真题解析)
合集内排序先视频后图文,各自内部按 priority_score 降序
最低开合集条目数视频 10 条 + 图文 10 条同时到位才开
付费墙视频全部会员专属,图文免费
完结状态永不完结

3.1 硬性过滤条件(不通过则跳过,不入任何队列)

Section titled “3.1 硬性过滤条件(不通过则跳过,不入任何队列)”
过滤项规则
时长(视频/音频)< 60 秒 或 > 5400 秒
内容语言AI 判断日语内容占比 < 30%
来源黑名单channel_id 在黑名单表
重复内容URL 的 MD5 已存在数据库
情况处理方式发布状态
有日语字幕直接提取并清洗正常发布
无字幕,时长 ≤ 1800 秒Whisper 转录正常发布
无字幕,时长 > 1800 秒不转录,仅元数据发布,标注”暂无字幕”
Whisper 失败标注”字幕转录失败”仍发布
条件visibility
默认VISIBLE
付费墙内容MEMBERSHIP_VISIBLE
AI 判断疑似违规(置信度 > 0.9)INVISIBLE,进异常队列

{
"title": "string,原始标题",
"channel_name": "string,频道/节目名",
"description": "string,描述前 300 字",
"transcript_sample": "string,字幕/正文前 500 字(无则空字符串)",
"duration_sec": "integer(图文填0)",
"source_tags": "array<string>,来源平台原有标签",
"content_type": "string,video / audio / article"
}
{
"channel": "string,7个栏目选1",
"level": "string,N5/N4/N3/N2/N1/不限",
"primary_scene": "string,从59个 sub_scene 名称中选1个(见第十三章完整列表)",
"secondary_scenes": "array<string>,从59个 sub_scene 名称中选0–2个(可为空数组)",
"priority_score": "integer,1–100,合集内排序依据(基于用户痛点匹配度:旅行/动漫/JLPT备考等高优先级痛点对应的内容分数高)",
"collection_hints": {
"suitable": "boolean",
"collection_name": "string(suitable=false时填空字符串)",
"reason": "string,≤50字"
},
"confidence": "float,0.0–1.0"
}

注意

  • 标题重写(title_rewrite)由独立的标题处理规则决定,不在此 Prompt 中处理
  • topic_tags 字段已废弃,改为直接使用 primary_scene / secondary_scenes(sub_scene 名称字符串)
  • 服务端通过 sceneMap[name] 将名称映射到 scene ID,匹配失败时不打标(不阻断发布)
confidence处理方式
≥ 0.75全字段直接写入
0.50–0.74写入,channel 改为”未分类”
< 0.50写入,channel=“未分类”,level=“不限”,topic_tags=[]

所有内容无论置信度如何,均发布(visibility = VISIBLE)。

LLM 在 1–100 范围内打分,评分依据:

匹配用户痛点程度priority_score 范围
直接命中高频痛点(旅行场景/动漫台词/JLPT备考/文化理解)75–100
间接相关(知识延伸/泛文化内容)40–74
弱相关(资讯/背景知识)10–39

五、定期智能分析任务规格(冷启动期版本)

Section titled “五、定期智能分析任务规格(冷启动期版本)”

⚠️ 冷启动期原则:当前阶段目标是极速扩充视频和音频内容(视频 25→500 条,播客 0→50 条),所有任务以”发现更多内容、加速入库”为唯一目的。冷启动期内不执行任何内容下架或降权操作。

冷启动期结束标志:视频内容 ≥ 500 条 + 播客内容 ≥ 50 条。

任务 A:合集归档分析(每周一次,周一 03:00 JST)

Section titled “任务 A:合集归档分析(每周一次,周一 03:00 JST)”

分析逻辑

  1. 扫描过去 7 天新入库内容
  2. primary_topic_tag 聚合,统计每个主题在过去 30 天的累计存量
  3. 某主题累计存量首次达到 10 条 → 生成合集建议

待办事项机制

  • 合集建议写入 collection_suggestions
  • 每次任务运行前,先扫描 collection_suggestions 表中所有状态 = pending 的建议
  • 对每条 pending 建议,AI 重新评估并附上”建议操作”(创建合集 / 暂缓 / 忽略)
  • 将评估结果一并发送飞书通知给 cc,让 cc 一次性处理所有待办
  • 未处理的建议不重复通知,保持在 pending 状态,等下次任务时再次评估和合并通知

飞书通知格式

📦 每周合集归档分析 - 2026-04-07
【待处理建议(3条)】
1. [新] 主题「动漫」过去30天累计 12条,建议建合集「动漫日语精听补充包」
→ 建议操作:立即创建(内容已达最低阈值8条)
2. [已提醒2周] 主题「旅行」累计 9条,建议建合集「去日本旅行必备」补充
→ 建议操作:再等1周(距阈值还差1条)
3. [新] 主题「职场商务」累计 11条,建议建合集「职场日语速成」
→ 建议操作:立即创建
👉 操作入口:[Admin 后台合集管理]
或回复此消息:「全部接受」「忽略第2条」等

cc 的处理方式:回复 Captain 消息即可触发操作,无需打开 Admin 后台。

任务 B:视频内容扩充追踪(每日一次,每日 09:00 JST)

Section titled “任务 B:视频内容扩充追踪(每日一次,每日 09:00 JST)”

分析逻辑

  1. 统计过去 24 小时新入库视频数量
  2. 统计当前视频总存量
  3. 统计各主题的视频存量分布

触发通知条件(以下任一满足则发通知):

  • 过去 24 小时视频新增 = 0(采集可能出现问题)
  • 某个主题视频存量达到合集开合阈值(8 条)

日报格式(仅在触发条件满足时发送,不发日常日报)

📹 视频库今日动态
总存量:87条(昨日 +5条)
距离冷启动期结束:413条
⚠️ 今日零新增来源:动漫频道(上次更新 3天前)
✅ 主题「旅行」已达合集开合阈值(8条),可创建合集

任务 C:热点内容采集建议(每周一次,周五 10:00 JST)

Section titled “任务 C:热点内容采集建议(每周一次,周五 10:00 JST)”

分析逻辑

  1. 检查近期日本热点(通过 NHK、Yahoo 新闻标题聚合分析)
  2. 对比现有内容库,找出热点主题在库内存量 < 5 条的空白点
  3. 生成当周内容采集建议清单

输出格式(飞书通知)

📋 本周内容采集建议 - 2026-04-04
【当前热点 × 库内空白】
1. 话题:「新年号」相关日语表达
库内存量:0条
建议搜索词:「令和 日本語 表現」「新元号 会話」
建议频道:NHK World YouTube
2. 话题:春季动漫新番开播
库内存量:3条(不够建合集)
建议搜索词:「2026春アニメ 公式PV」
建议频道:东宝/角川官方频道
👉 发链接给我即可触发自动入库

频道名YouTube Handle内容方向采集优先级
NHK World Japan@NHKWorldJapanEng文化/科技/社会纪录片P0
NHK Web Easy@nhk_easy简单日语新闻视频版P0
東宝(Toho)Animation@TohoAnimation动漫 PV + 第1话P1
角川(Kadokawa)官方@KADOKAWA_official动漫 PV + 试看P1
集英社(Shueisha)官方@ShueishaOfficial少年漫画动漫 PVP1
日本政府观光局 JNTO@JNTO_jp旅行文化视频P1
目标内容日文搜索词英文搜索词目标合集
旅行场景对话日本語 旅行 会話 練習Japanese travel phrases去日本旅行必备
机场/酒店场景空港 ホテル 日本語Japanese airport hotel phrases去日本旅行必备
餐厅点餐レストラン 日本語 注文Japanese restaurant ordering去日本旅行必备
JLPT N3 词汇JLPT N3 語彙 解説JLPT N3 vocabularyJLPT N3 冲刺
JLPT N3 语法N3 文法 例文 解説JLPT N3 grammarJLPT N3 冲刺
日本文化讲解日本文化 解説 面白いJapanese culture explained日本文化冷知识
动漫台词解析アニメ 日本語 解説anime Japanese dialogue动漫日语精听
日本节日习俗日本 祭り 伝統 解説Japanese festivals热门专题
节目名平台RSS 状态难度更新频率优先级
NHK ラジオ日本NHK / Apple PodcastRSS 公开N3–N4每日P0
Nihongo con TeppeiApple PodcastRSS 公开N3–N4每日P0
Erin’s ChallengeApple PodcastRSS 公开N5–N3不定期P1
Bilingual NewsApple PodcastRSS 公开N2–N1每周P2
周次执行内容预期新增
第 1 周配置 NHK World Japan YouTube 批量采集+50–100 条视频
第 2 周配置 NHK Radio RSS + Nihongo con Teppei+播客 10–20 条
第 3 周配置东宝/角川/集英社动漫官方频道+30–60 条视频
第 4 周配置 JNTO 旅行局 + 主动搜索旅行关键词+20–40 条视频

用户痛点典型说法对应首页模块对应合集当前缺口
看动漫听不懂”追了10年番,一句话听不懂”动漫 Tab + 动漫合集动漫日语精听❌ 视频待采集
旅行交流障碍”单词都会,现场说不出来”场景日语去日本旅行必备❌ 视频待采集
不知自己水平”不知道从哪开始学”JLPT 专区 + 轻松入门JLPT N3 冲刺✅ 图文已满足
学习太枯燥”几天就放弃”动漫 Tab + 播客系列动漫日语精听❌ 待采集
不懂文化背景”语言学了,语境不懂”日本文化栏目日本文化冷知识❌ 视频待采集
坚持不了学习”三天打鱼两天晒网”今日学习区(15分钟目标)⚠️ 机制待设计

选项(单选,注册必经步骤)

  1. 追番 / 看懂动漫日剧
  2. 去日本旅行
  3. JLPT 备考
  4. 了解日本文化
  5. 随便看看 / 保持语感

偏好存储字段users.interest_category(enum: anime / travel / jlpt / culture / casual)

冷启动期的使用方式

  • 注册时记录偏好,但当前阶段不做个性化推荐(内容量不足)
  • 偏好数据预留,不消费,等视频内容 ≥ 500 条后启用个性化逻辑
  • 注册选完后,首页展示与选项匹配的”欢迎语”(如”为追番用户推荐的入门路径”)+ 通用内容

游客默认状态:全部按通用版展示,不做任何个性化。


CREATE TABLE collections (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(30) NOT NULL COMMENT '合集标题,≤14中文字',
description VARCHAR(150) NOT NULL COMMENT '合集简介,≤30中文字',
cover_url VARCHAR(500) NOT NULL COMMENT '封面图 OSS 地址',
collection_type ENUM('rolling_feed','curated','series') NOT NULL
COMMENT 'rolling_feed=日更自动追加; curated=人工筛选; series=播客/连载系列',
channel VARCHAR(30) NOT NULL COMMENT '归属栏目(与 news.channel 对应)',
level VARCHAR(10) NOT NULL DEFAULT '不限' COMMENT 'N5/N4/N3/N2/N1/不限',
is_membership TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0=免费;1=会员专属(整个合集)',
is_finished TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0=持续更新;1=已完结',
is_visible TINYINT(1) NOT NULL DEFAULT 1 COMMENT '0=隐藏;1=展示',
item_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '合集内容数(冗余字段,定期更新)',
sort_order INT NOT NULL DEFAULT 0 COMMENT '首页排序权重,后台手动设置,越大越靠前',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE collection_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
collection_id BIGINT UNSIGNED NOT NULL,
news_id VARCHAR(36) NOT NULL COMMENT '对应 news 表的 UUID',
priority_score TINYINT UNSIGNED NOT NULL DEFAULT 50 COMMENT 'LLM 打分 1-100,合集内排序依据',
is_free TINYINT(1) NOT NULL DEFAULT 1 COMMENT '单条内容是否免费(覆盖合集付费墙)',
sort_position INT NOT NULL DEFAULT 0 COMMENT '手动排序位(curated 类合集使用)',
added_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_collection_id (collection_id),
INDEX idx_news_id (news_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

8.3 合集内容排序规则(按 collection_type)

Section titled “8.3 合集内容排序规则(按 collection_type)”
类型排序规则
rolling_feedadded_at DESC(最新入库在前)
curatedsort_position ASC(手动排序),sort_position 相同则 priority_score DESC
seriesadded_at ASC(第 1 期在前,追更逻辑)

8.4 用户合集观看进度(user_collection_progress 表)

Section titled “8.4 用户合集观看进度(user_collection_progress 表)”
CREATE TABLE user_collection_progress (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
collection_id BIGINT UNSIGNED NOT NULL,
last_news_id VARCHAR(36) COMMENT '最近看到的内容 ID',
watched_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已完成条目数',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_collection (user_id, collection_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

合集详情页展示

  • 顶部显示”已看 X 集 / 共 X 集”进度
  • 已看过的条目在列表中以灰色/已完成样式显示(复用 NHK News 现有已读样式)
  • 看完一集自动跳转下一集(is_auto_play = true,默认开启,可关闭)

行为计入有效学习时长计入条件
视频播放播放进度推进(非挂后台)
音频播放播放进度推进
文章阅读在文章详情页停留(每秒累积,最长计 5 分钟/篇防止挂页面)
App 后台不计入
首页浏览不计入
条件
每日打卡目标累计有效学习时长 ≥ 15 分钟
Streak 递增时机当日 23:59 JST 时检查,≥15 分钟则 +1
Streak 重置条件连续 2 天累计有效时长 < 15 分钟(给 1 天缓冲)
复活次数付费用户每 30 天 1 次
  • 今日学习区:进度条显示”今日已学 X 分钟 / 15 分钟”
  • 达到 15 分钟 → 打卡成功动画 + Streak +1
  • 超过 15 分钟继续计数,显示”今日已超额 X 分钟”(鼓励机制)

原则:沿用 NHK 现有标题处理方式(假名标注 + 中文对照),不重写

现有流水线已处理:title(日语原标题)/ title_annotation(假名标注)/ title_html(含注音 HTML)。

原则:不使用 LLM 问题式重写,而是将原日语/英语标题翻译为地道中文标题,风格参考 NHK 中文网的标题写法(简洁直白,保留原意,不夸张)。

处理流程

  1. 原始标题(日/英)→ DeepSeek 翻译为中文(≤30字)
  2. 中文标题写入 title_zh(新增字段)
  3. 前端展示:详情页顶部显示 title_zh,副标题显示原日语 title
  4. LLM title_rewrite 字段仅用于合集内的内容卡片标题(可以更吸引眼球),不影响详情页标题

合集卡片标题(title_rewrite)规则

  • LLM 生成,≤20字,“问题/场景”表述
  • 例:原标题「桜の季節の日本語表現」→ 合集卡片显示「日本赏樱时必说的5句话」
  • 置信度 < 0.5 时,合集卡片直接用 title_zh

内容类型封面来源存储方式
NHK 图文资讯爬取文章原始封面图已有,复用现有 OSS 流程
YouTube 视频YouTube 缩略图(maxresdefault.jpg → hqdefault.jpg fallback)下载到 OSS(防止 YouTube 失效404)
Apple PodcastRSS <itunes:image> 中的图片 URL下载到 OSS
合集封面运营人工上传 or 程序自动取合集第1条内容的封面上传到 OSS
播客系列封面RSS 节目封面图下载到 OSS
视频 ID = extracted_from_url(url)
尝试顺序:
1. https://img.youtube.com/vi/{video_id}/maxresdefault.jpg (1280×720)
2. https://img.youtube.com/vi/{video_id}/hqdefault.jpg (480×360)
3. https://img.youtube.com/vi/{video_id}/mqdefault.jpg (320×180)
获取成功后:下载图片 → 上传到 OSS → 写入 news.image_url = OSS地址
获取失败(HTTP 404 全部失败):image_url = 默认占位图地址

11.3 合集封面自动生成逻辑(curated 合集,运营未上传时)

Section titled “11.3 合集封面自动生成逻辑(curated 合集,运营未上传时)”
合集封面 fallback 规则:
取合集内 priority_score 最高的1条内容的封面图
叠加合集标题文字(白色字体,右下角水印)
写入 collections.cover_url

本节持续更新,记录所有尚未定义清楚的问题。已解决的条目标记 ✅ 并移入对应章节。

问题结论所在章节
topic_tags 和 scenes 两套体系前端如何使用?不新建 topic_tags 表(见下方P0-1修正);scenes 体系前端展示归并到 scene 中类;Tab 分类由 scene_id 范围决定第十三章
合集付费墙 UI 表现复用现有 premium VIP 标签机制,visibility=MEMBERSHIP_VISIBLE 自动打 VIP 标签;前端已有实现第十三章
视频详情页播放器已有实现,复用 movie_url 字段即可,不需新开发第十三章
合集内容排序规则rolling_feed 按 added_at DESC;curated 按 sort_position + priority_score;series 按 added_at ASC第八章
封面图来源与存储YouTube 缩略图下载到 OSS;合集封面取最高 priority_score 内容封面作 fallback第十一章

🔴 P0(当前仍未定义,会卡住执行)

Section titled “🔴 P0(当前仍未定义,会卡住执行)”

P0-1:是否需要新建 topic_tags 表?

修正判断:经工程分析,不建议新建 topic_tags

原因:

  • 现有 scenes 表三层结构(category → scene → sub_scene)已经能覆盖所有分类需求
  • 新建表意味着 LLM 需要输出两套独立标签、服务端需要写两张关联表、前端需要合并展示——工程成本高,收益低
  • 所有”主题归属”需求(Tab 分类、合集归档、热点追踪)都可以通过 scene.category + scene.scene(中类)实现

结论

  • 废弃 topic_tags / news_topic_tags 表的设计
  • LLM 打标输出只需 primary_scene + secondary_scenes(sub_scene 级别),写入现有 news_scenes
  • Tab 分类通过 scene_id 范围映射(见第十三章映射表)
  • 合集归档分析通过 scene.scene(中类)聚合,而不是独立的 topic_tags

对规格文档其他章节的影响

  • 第四章 LLM Prompt 输出 schema 中的 primary_topic_tag / secondary_topic_tags 字段 → 改为 primary_scene_id + secondary_scene_ids(直接输出 scene ID)
  • 第五章定期任务 A 中”按 primary_topic_tag 聚合” → 改为”按 scene.scene 中类聚合”
  • 第一章 Tab 分类逻辑 → 已改为 scene_id 范围映射

P0-2:LLM 打标输出如何精确映射到 scene_id?

LLM 输出自然语言标签(如”动漫文化”),服务端需要把它映射到数据库 scene.sub_scene 的 ID(1–59)。

两种方案

  • 方案 A:LLM 直接输出 scene ID(数字),Prompt 里把 59 个 sub_scene 全部列出 → 简单,但 Prompt 长
  • 方案 B:LLM 输出 sub_scene 名称字符串,服务端查 sceneMap[name] 映射到 ID → 灵活,但名称可能匹配失败

建议方案 B:LLM 输出 sub_scene 名称,服务端用精确字符串匹配(已有 sceneMap 实现),失败时不打标(不阻断发布)。

已有代码参考news_scenes.go 中的 sceneMap[tag] 逻辑完全可复用,只需扩展 Prompt 让 LLM 从 59 个 sub_scene 名称中选择。


🟡 P1(影响运营,近期需要定义)

Section titled “🟡 P1(影响运营,近期需要定义)”

P1-1:热门专题(模块⑧)的数据结构

建议方案:复用 collections 表,新增 is_topic TINYINT(1) 字段区分合集(0)和热门专题(1)。

理由:

  • 专题在产品形态上与合集几乎相同(封面 + 标题 + 内容列表)
  • 唯一差异:专题有时效性(14天自动下线),合集长期有效
  • 用一张表管理,后台管理界面可以统一

新增字段

ALTER TABLE collections ADD COLUMN is_topic TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0=合集;1=热门专题';
ALTER TABLE collections ADD COLUMN expire_at DATETIME NULL COMMENT '专题到期时间(is_topic=1时有效,NULL=永不过期)';

定时任务:每日 00:00 JST 检查 expire_at < NOW() AND is_topic = 1,自动设 is_visible = 0


P1-2:今日推荐(模块②)的选取逻辑

建议方案:后端定时任务每日 06:00 JST 生成,写入 daily_featured 配置表。

CREATE TABLE daily_featured (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
date DATE NOT NULL COMMENT '日期(JST)',
news_id VARCHAR(36) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_date (date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

选取逻辑(按优先级):

  1. type = video + level = 3(N3) + published_at = 今日
  2. type = video + 任意 level + published_at = 今日
  3. type = article + level = 3(N3) + published_at = 今日
  4. 兜底:published_at = 今日,取第一条

API 查询SELECT news_id FROM daily_featured WHERE date = CURDATE(),无结果时实时计算兜底。


P1-3:合集 priority_score 由谁生成?何时生成?

当前状态:LLM 打标时同步生成 priority_score(1–100),在 collection_hints 中返回。

写入时机

  • 内容入库时(AddSceneToNews 之后),如果 collection_hints.suitable = true,写入 collection_items.priority_score
  • 非合集内容的 priority_score 存在 news 表新增字段 priority_score 中(用于合集归档时的排序参考)

当前不需要立即实现,合集功能上线前可先用默认值 50。


🟢 P2(长期,不影响首期上线)

Section titled “🟢 P2(长期,不影响首期上线)”

P2-1:用户学习时长的客户端统计方案

每日 15 分钟打卡目标需要客户端统计有效时长。具体实现:

  • iOS 需要在 AppDelegate/SceneDelegate 中监听 foreground/background 事件
  • 视频播放进度监听(AVPlayer timeObserver
  • 文章页停留时间记录(viewWillAppear/viewWillDisappear)
  • 需要 iOS 开发(少琛)评估工作量

P2-2:合集自动播放下一集的逻辑

用户看完一集视频后,自动播放下一集。需要:

  • user_collection_progress 表记录当前看到哪一集
  • 视频播放完成回调触发”下一集预加载”
  • 需要 iOS 开发评估

文档性质:内容系统实施规格,持续补全中 当前状态:冷启动扩张期——视频 25 条→目标 500 条,播客 0 条→目标 50 条 创建:2026-03-27 | 最后更新:2026-03-28(附录D:产品运营定义层,15个卡点,Onboarding流程+权益对比+运营日历+版权规范+通知策略)

内容系统实施规格 · 深度自查报告

Section titled “内容系统实施规格 · 深度自查报告”

以下是对 yomiya-implementation-spec.md 当前版本的全面漏洞扫描, 按严重程度分级,每个问题给出具体原因和修复建议。


🔴 致命漏洞(会导致实施方向错误或系统无法运转)

Section titled “🔴 致命漏洞(会导致实施方向错误或系统无法运转)”

漏洞 1:第 1.5 节与第 13 章存在直接矛盾

Section titled “漏洞 1:第 1.5 节与第 13 章存在直接矛盾”

问题:第 1.5 节(每日资讯 Tab 区)仍然写的是 topic_tag → Tab 映射表(如”社会观察→社会”),但第 12 章已经决定废弃 topic_tags 体系,改用 scene_id 范围映射。

两套逻辑同时存在,工程实现时一定会产生歧义。

修复:删除第 1.5 节的 topic_tag 映射表,改为 scene_id 范围映射(与第 13 章一致)。


漏洞 2:任务 A 仍然使用已废弃的 primary_topic_tag 字段

Section titled “漏洞 2:任务 A 仍然使用已废弃的 primary_topic_tag 字段”

问题:第五章任务 A 写”按 primary_topic_tag 聚合”,但 topic_tags 已废弃。服务端实现时查这个字段会报错。

修复:改为”按 scene.scene 中类聚合”,并给出具体 SQL:

SELECT s.scene as scene_mid, COUNT(*) as cnt
FROM news_scenes ns
JOIN scenes s ON ns.scene_id = s.id
WHERE ns.scene_primary = 'primary'
AND ns.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY s.scene
HAVING cnt >= 10

漏洞 3:rolling_feed 合集的”自动追加”机制完全没有工程定义

Section titled “漏洞 3:rolling_feed 合集的”自动追加”机制完全没有工程定义”

问题:「NHK 今日要闻」和「日本娱乐速报」是 rolling_feed 类型,每日自动把新内容追加进来。但文档里完全没有说这个追加逻辑在哪里实现、如何触发。

这是 P0 级工程需求,没有它这两个合集根本无法运转。

修复:需要明确说明:

  • 追加规则存在哪里(collections 表新增 auto_append_rule JSON 字段?)
  • 追加逻辑在哪里运行(内容入库流水线的最后一步?还是独立定时任务?)
  • 追加条件是什么(channel_id = NHK 的内容入库时自动检查所有 rolling_feed 合集的规则)

漏洞 4:Podcast 采集链路没有工程实现路径

Section titled “漏洞 4:Podcast 采集链路没有工程实现路径”

问题:采集清单里列了 NHK Radio、Nihongo con Teppei 等播客,但:

  • 工程里现有的采集链路全部针对图文(NHK Web Easy)和 YouTube 视频
  • 播客 RSS 采集是全新链路,需要新开发
  • 音频内容的字幕从哪里来(RSS 文字稿?Whisper?)完全未定义
  • 音频内容存哪个字段(movie_url?还是新加 audio_url?)

修复:需要补充播客采集的技术方案:

  1. RSS 解析 → 获取每期音频 MP3 直链
  2. 下载音频 → 上传 OSS
  3. 字幕策略:RSS description 作为文字稿,无则 Whisper 转录
  4. 存储:news.movie_url = OSS 音频地址news.type = audio(现有 type 枚举已有 audio)

漏洞 5:news 表新增字段没有 ALTER TABLE 定义

Section titled “漏洞 5:news 表新增字段没有 ALTER TABLE 定义”

问题:规格提到:

  • title_zh(视频中文标题)- 只在第 10 章提了概念
  • priority_score(优先级评分)- 第 12 章提到要加但没写 SQL
  • word_count(第 1.7 节用到了)- 未确认是否存在

若这些字段不存在,相关逻辑全部无法实现。

修复

ALTER TABLE news ADD COLUMN title_zh VARCHAR(100) NULL COMMENT '视频内容中文标题(DeepSeek翻译)';
ALTER TABLE news ADD COLUMN priority_score TINYINT UNSIGNED NOT NULL DEFAULT 50 COMMENT 'LLM内容优先级评分 1-100';
-- word_count 需要先确认是否存在,不存在则:
ALTER TABLE news ADD COLUMN word_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '文章字数(图文内容)';

漏洞 6:collection_suggestions 表没有定义

Section titled “漏洞 6:collection_suggestions 表没有定义”

问题:任务 A 说”合集建议写入 collection_suggestions 表”,但没有 CREATE TABLE,没有字段定义,没有状态枚举。工程无法实现。

修复

CREATE TABLE collection_suggestions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
scene_mid VARCHAR(50) NOT NULL COMMENT 'scene 中类名称(聚合维度)',
item_count INT UNSIGNED NOT NULL COMMENT '过去30天该主题内容数',
suggested_name VARCHAR(30) NOT NULL COMMENT '建议合集名称',
status ENUM('pending','accepted','rejected','deferred') NOT NULL DEFAULT 'pending',
ai_recommendation TEXT COMMENT 'AI 给出的建议操作文本',
notified_at DATETIME NULL COMMENT '上次通知 cc 的时间',
notify_count TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已通知次数',
expire_at DATETIME NULL COMMENT '建议过期时间(30天后若未处理自动 rejected)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

🟠 严重缺失(不补会导致实施混乱)

Section titled “🟠 严重缺失(不补会导致实施混乱)”

缺失 7:Admin 后台能力完全未定义

Section titled “缺失 7:Admin 后台能力完全未定义”

整个 826 行文档,没有一处定义 Admin 后台需要支持哪些操作。

合集管理是核心运营工具,没有它运营无法执行:

  • 创建/编辑合集(标题/描述/封面/类型/付费墙)
  • 向合集添加/移除内容
  • 调整合集首页排序(sort_order)
  • 创建/管理热门专题
  • 手动覆盖今日推荐
  • 查看并处理合集建议(collection_suggestions)
  • 查看异常队列

这些不是可选的——没有 Admin 能力,运营完全无法使用内容系统。


缺失 8:新视频内容的 channel 归属没有定义

Section titled “缺失 8:新视频内容的 channel 归属没有定义”

问题:NHK World YouTube 视频、动漫官方 PV 这些新采集的视频,应该属于哪个 channel

现有 channels 表只有 5 个图文频道(NHK/Yahoo/Hukumusume 等)。视频内容需要新的频道。

没有定义:

  • 是否新建 channel(如 NHKWorldVideoAnimeOfficial)?
  • 新 channel 的 display_namedescriptionimg_url 是什么?
  • 视频内容的 channel 和合集的 channel 有什么关系(一条内容的 channel 和它归属的合集的 channel 可以不同吗?)

缺失 9:内容看完后的”下一步”行为未定义

Section titled “缺失 9:内容看完后的”下一步”行为未定义”

问题:用户看完一条视频/文章后,App 展示什么?这直接影响用户留存和继续消费路径。

可能的设计:

  1. 自动播放合集下一集(如果内容属于某合集)
  2. 显示”相关内容推荐”(3条 same scene 的内容)
  3. 显示”收藏/分享”操作
  4. 跳回合集详情页
  5. 跳回首页

没有定义这个核心用户旅程,iOS 开发无法实现,用户体验会是黑洞。


问题:内容系统有付费墙(VIP 内容),但触发付费的路径完全没有定义:

  • 用户点击锁定内容后,看到什么 UI?
  • 弹出付费引导弹窗?还是直接跳转会员购买页?
  • 弹窗的文案是什么?有几个 CTA?
  • 这和 App 现有的付费墙 UI 是否统一?(现有已有 RevenueCat 集成)

没有这个定义,付费墙就只是展示一个锁,无法转化。


问题

  1. daily_featured 表用 CURDATE(),但服务器时区未必是 JST(可能是 UTC)
  2. 定时任务在”JST 时间”运行,但 MySQL 服务器可能在 UTC
  3. Streak 在”23:59 JST”结算,但客户端时区和服务端时区可能不一致

如果服务器是 UTC,JST 的 00:00 = UTC 的前一天 15:00,整个打卡逻辑会跨天错误。

修复:明确定义所有时间计算统一使用 CONVERT_TZ(NOW(), 'UTC', 'Asia/Tokyo'),并在 Go 服务端的时区配置中明确。


问题:用户如何找到内容?目前只定义了首页模块,但没有定义:

  • 搜索功能:是否存在?支持搜索什么(标题?场景标签?)
  • 内容详情页→所属合集入口:看某条内容时,能否看到”该内容属于XX合集”并跳转?
  • 标签页筛选:是否有”按场景/难度/类型”筛选内容的页面?
  • 合集入口的多样性:合集只能从首页”热门推荐”发现吗?JLPT专区内的内容能跳到对应合集吗?

内容发现路径单一会导致大量内容被埋没。


遗漏 13:视频内容的 channel 与合集的关系未定义

Section titled “遗漏 13:视频内容的 channel 与合集的关系未定义”

一条视频内容,同时有:

  • news.channel(频道归属,如 NHKWorldVideo
  • collection_items.collection_id(合集归属,如”日本文化冷知识”)

两者是否可以不同?一条内容可以属于多个合集吗?(collection_items 表设计允许,但业务规则未定义)


遗漏 14:Banner 轮播的数据结构未定义

Section titled “遗漏 14:Banner 轮播的数据结构未定义”

第 1.2 节说”后台配置 3 张图”,但没有说:

  • 对应哪张表?
  • 每张 Banner 的字段:图片 URL / 跳转类型(合集/专题/付费页)/ 跳转 ID / 有效期
  • 这些已有实现吗?还是新建?

遗漏 15:合集详情页规格未定义

Section titled “遗漏 15:合集详情页规格未定义”

完整的合集详情页需要:

  • 顶部:合集封面 + 标题 + 简介 + 集数 + 难度标签 + 付费墙说明
  • 内容列表:已看/未看状态 + 标题 + 时长 + 免费/VIP 标签
  • 进度条:“已看 X 集 / 共 X 集”
  • 行动按钮:“继续看”(跳到 last_news_id 对应位置)

这些在文档里只有片段,不够完整给 iOS 开发参考。


遗漏 16:内容重复采集的精确定义

Section titled “遗漏 16:内容重复采集的精确定义”

过滤规则写的是”URL 的 MD5 已存在则跳过”。但:

  • 同一个 YouTube 视频可能有多种 URL 格式(youtu.be 短链、youtube.com 完整链、带时间戳的链接)
  • 规范化应该是用 video_id 而不是 URL MD5
  • 没有定义 YouTube URL 如何提取 video_id 作为去重依据

遗漏 17:合集的”会员专属”和单条内容的”会员专属”冲突时如何处理

Section titled “遗漏 17:合集的”会员专属”和单条内容的”会员专属”冲突时如何处理”

场景:合集 is_membership = 0(合集整体免费),但某条内容 is_free = 0(该条会员专属)。

反过来:合集 is_membership = 1(整个合集会员专属),但某条内容 is_free = 1

这两个字段的优先级谁高?文档没有说。

建议is_free 字段(collection_items 级别)覆盖 is_membership(collections 级别),即:即使合集整体是会员专属,is_free=1 的条目仍然对所有用户可见。


遗漏 18:Whisper 转录的成本和配额控制

Section titled “遗漏 18:Whisper 转录的成本和配额控制”

规格说”无字幕视频 ≤ 30 分钟的触发 Whisper 转录”,但:

  • Whisper 转录是有成本的(Azure Speech API 按分钟计费)
  • 冷启动期要快速采集 500 条视频,如果大量视频需要转录,成本会激增
  • 没有定义:是否设置每日转录分钟数上限?优先级策略(先转录哪些)?

🟢 优化建议(不影响功能但影响产品质量)

Section titled “🟢 优化建议(不影响功能但影响产品质量)”

建议 19:priority_score 的评分逻辑过于主观

Section titled “建议 19:priority_score 的评分逻辑过于主观”

当前定义是 LLM 根据”用户痛点匹配度”打 1-100 分,但:

  • 不同内容的绝对分数没有参照系(75-100 是”直接命中”,但如何判断?)
  • LLM 的评分会有随机性,导致同质内容的排序不稳定
  • 建议改为基于 sub_scene 中类的固定权重表,而不是 LLM 自由发挥:
旅游(15-18)→ 90
动漫(27-30)→ 85
备考(1-4) → 80
文化(44-48)→ 70
影视(31-34)→ 65
... 以此类推

建议 20:冷启动期结束后的”精细化运营期”没有任何定义

Section titled “建议 20:冷启动期结束后的”精细化运营期”没有任何定义”

文档定义了冷启动期(视频<500条),但精细化运营期的规则完全空白。 至少应该写一行”冷启动期结束后,启用以下机制:…”,哪怕是占位符。


自查结论:当前文档在概念层(策划层)已经相当完整,但在实施层(工程层)有 6 个致命漏洞、6 个严重缺失,这些问题一旦进入开发阶段会立刻暴露。

最高优先级需要补的 3 件事:

  1. 修复第 1.5 节与第 13 章的矛盾(Tab 分类逻辑统一)
  2. 定义 rolling_feed 合集的自动追加机制(工程无法实现)
  3. 定义 Admin 后台能力清单(运营无法操作内容系统)

本附录记录对自查报告中所有漏洞的修复方案,每处说明”怎么补”和”为什么这样补”。


修复 1:第 1.5 节 Tab 分类逻辑统一为 scene_id 范围映射

Section titled “修复 1:第 1.5 节 Tab 分类逻辑统一为 scene_id 范围映射”

原问题:第 1.5 节用 topic_tag 名称映射 Tab,第 13 章用 scene_id 范围映射,两套逻辑矛盾。

修复方案:删除第 1.5 节的 topic_tag 映射表,统一使用 scene_id 范围映射:

每日资讯 Tab → scene_id 范围(完整定义)

Tab对应 scene 中类scene_id 范围后端查询条件
社会新闻49–52scene_id IN (49,50,51,52)
娱乐影视 + 音乐31–38scene_id IN (31,32,33,34,35,36,37,38)
文化传统文化 + 生活 + 商务 + 留学 + 文学11–26, 39–48scene_id BETWEEN 11 AND 26 OR scene_id BETWEEN 39 AND 48
动漫动漫27–30scene_id IN (27,28,29,30)
科技科技53–56scene_id IN (53,54,55,56)
旅行旅游15–18scene_id IN (15,16,17,18)
兜底(无法映射时)→ 社会 Tab默认归入社会

体育(57-59):暂归入社会 Tab,等体育内容积累到 30 条后单独开 Tab。

为什么:scene_id 是数据库中实际存在的字段,服务端可以直接用 IN 查询,不需要任何字符串匹配或转换逻辑。topic_tag 已废弃,不应在任何地方继续出现。


修复 2:任务 A 聚合逻辑改用 scene 中类

Section titled “修复 2:任务 A 聚合逻辑改用 scene 中类”

原问题:任务 A 写”按 primary_topic_tag 聚合”,topic_tags 已废弃。

修复:改为如下 SQL,按 scene 中类聚合:

-- 合集归档分析:统计过去30天各 scene 中类的内容新增数
SELECT
s.scene AS scene_mid,
COUNT(DISTINCT ns.news_id) AS item_count
FROM news_scenes ns
JOIN scenes s ON ns.scene_id = s.id
JOIN news n ON ns.news_id = n.id
WHERE ns.scene_primary = 'primary'
AND n.created_at >= DATE_SUB(CONVERT_TZ(NOW(), 'UTC', 'Asia/Tokyo'), INTERVAL 30 DAY)
AND n.deleted_at IS NULL
GROUP BY s.scene
HAVING item_count >= 10
ORDER BY item_count DESC;

为什么scene.scene 字段是中类名称(如”动漫”、“旅游”、“新闻”),13 个中类直接对应产品的栏目/合集维度,是最自然的聚合粒度。比 sub_scene(59个太细)和 category(4个太粗)更合适。


修复 3:rolling_feed 合集自动追加机制

Section titled “修复 3:rolling_feed 合集自动追加机制”

原问题:「NHK 今日要闻」每日自动追加,但追加规则存在哪、怎么触发完全未定义。

修复方案

新增字段到 collections 表

ALTER TABLE collections ADD COLUMN auto_append_rule JSON NULL
COMMENT 'rolling_feed 类型的自动追加规则,格式见下方';

auto_append_rule JSON 格式

{
"channel_name": "NHK",
"scene_ids": [],
"level_range": [1, 4],
"content_types": ["article", "video", "audio"]
}
  • channel_name:指定频道名,为空则不按频道过滤
  • scene_ids:指定场景 ID 列表,为空则不按场景过滤
  • level_range:难度范围 [min, max][1,5] 表示所有级别
  • content_types:内容类型白名单

触发时机:在现有内容入库流水线末尾(raw_news → news 第 13 步 MQ 消息之后),新增一步:

Step 14(新增):检查 rolling_feed 合集追加规则
→ 查询所有 is_visible=1 AND collection_type='rolling_feed' 的合集
→ 对每个合集,评估新入库内容是否满足 auto_append_rule 条件
→ 满足则写入 collection_items(added_at=当前时间,priority_score=50,is_free=1)
→ 更新 collections.item_count(+1)

首批合集追加规则

  • 「NHK 今日要闻」:{"channel_name":"NHK","scene_ids":[],"level_range":[1,5],"content_types":["article"]}
  • 「日本娱乐速报」:{"channel_name":"YahooNewsEntertainment","scene_ids":[],"level_range":[1,5],"content_types":["article"]}

为什么:追加逻辑嵌在入库流水线末尾,保证每条内容入库时立刻评估是否进合集,而不是靠定时任务轮询。定时任务有延迟,流水线嵌入是实时的。JSON 格式的规则存在数据库里,无需改代码就能调整规则。


修复 4:Podcast 采集链路技术方案

Section titled “修复 4:Podcast 采集链路技术方案”

原问题:播客采集是全新链路,没有任何实现路径。

修复方案

利用现有工程能力news.type = "audio" 枚举已存在,news.movie_url 字段可存音频 URL,无需新字段。

新增 Podcast RSS 采集器(参考现有爬虫结构):

PodcastImporter(新增,参考 YouTubeImporter 结构)
1. 读取 RSS Feed URL
2. 解析 XML:获取每期 title / description / pubDate / enclosure(MP3 URL) / itunes:image
3. 下载 MP3 到 OSS(保留原始文件,不做转码)
4. 写入 raw_news:
- title = RSS item title
- movie_url = OSS MP3 地址(复用 movie_url 字段存音频)
- type = "audio"
- image_url = RSS itunes:image 下载到 OSS 的地址
5. 触发现有流水线:假名标注(跳过,音频无文字稿)→ 翻译(跳过)→ 难度分析(基于描述文字)→ LLM 打标
字幕策略:
- 优先用 RSS description 作为文字稿(写入 news sentences)
- RSS 无 description 时:Whisper 转录(仅对时长 ≤ 30 分钟的音频,超过则跳过转录)

去重规则:用 RSS item 的 guid 字段做去重(类似 YouTube video_id),而非 URL MD5。

为什么用 movie_url 存音频而不新加字段news.type = "audio" 已有,iOS 和服务端都识别这个枚举,用 movie_url 传音频地址零额外改动,iOS 侧根据 type == audio 启用音频播放器而非视频播放器。


修复 5:news 表新增字段 ALTER TABLE

Section titled “修复 5:news 表新增字段 ALTER TABLE”

原问题:规格多处引用了不存在的字段,无 SQL 定义。

确认现有字段(已存在,无需新增):

  • title(日语标题)✅
  • movie_url(视频/音频 URL)✅
  • type(webpage/video/audio)✅
  • level(1-5)✅
  • visibility

确认不存在、需新增的字段

-- 视频/音频内容的中文标题(DeepSeek翻译)
ALTER TABLE news ADD COLUMN title_zh VARCHAR(100) NULL
COMMENT '视频/音频内容中文标题,由DeepSeek翻译生成,图文内容不填' AFTER title;
-- LLM 内容优先级评分(用于合集内排序)
ALTER TABLE news ADD COLUMN priority_score TINYINT UNSIGNED NOT NULL DEFAULT 50
COMMENT 'LLM内容优先级评分1-100,默认50,入库时由打标服务写入' AFTER level;

关于 word_count:查阅代码后确认 news 表中不存在 word_count 字段,且轻松入门区的”字数 ≤ 300”过滤可以改用 title 长度 + sentences 数量组合估算,暂不新增此字段,改用以下规则:

  • 图文内容:sentences 表中该 news 的行数 ≤ 5 作为”短文”判定(间接衡量字数)
  • 视频/音频:用 duration_sec ≤ 300 已足够

为什么:只加确定需要的字段,避免过度设计。word_count 需要单独统计逻辑,成本高于收益。


修复 6:collection_suggestions 表 CREATE TABLE

Section titled “修复 6:collection_suggestions 表 CREATE TABLE”

原问题:任务 A 引用这张表但没有定义。

CREATE TABLE collection_suggestions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
scene_mid VARCHAR(50) NOT NULL COMMENT 'scene 中类名称(如"动漫"、"旅游")',
item_count INT UNSIGNED NOT NULL COMMENT '过去30天该 scene 中类的内容数',
suggested_name VARCHAR(30) NOT NULL COMMENT 'AI 建议的合集名称(≤14字)',
status ENUM('pending','accepted','rejected','deferred') NOT NULL DEFAULT 'pending',
ai_advice TEXT NULL COMMENT 'AI 给出的建议操作文本(当次评估结果)',
notified_count TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已通知 cc 的次数',
last_notified_at DATETIME NULL COMMENT '上次通知时间',
resolved_at DATETIME NULL COMMENT '被处理的时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status (status),
INDEX idx_scene_mid (scene_mid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

为什么status 字段的四个状态:pending(待处理)/ accepted(已创建合集)/ rejected(忽略)/ deferred(暂缓,下次再看)。notified_count 控制不重复骚扰,同一条建议连续 3 周未处理后自动改为 deferred,不再出现在通知里。


修复 7:Admin 后台能力清单(最小可用版本)

Section titled “修复 7:Admin 后台能力清单(最小可用版本)”

原问题:整份文档没有定义 Admin 需要支持哪些操作。

Admin 后台功能清单(按优先级)

P0 — collections 表上线时必须同步完成

功能操作说明
合集列表查看所有合集(标题/类型/条目数/是否展示/排序权重)
创建合集填写标题/描述/类型/封面/channel/level/is_membership
编辑合集修改合集基础信息
合集内容管理添加/移除 news,调整 sort_position,设置 is_free
首页排序拖拽调整 sort_order,决定哪8个合集上首页
隐藏/显示合集切换 is_visible,不删除数据只隐藏

P1 — 合集上线后 2 周内补齐

功能操作说明
热门专题管理创建/编辑专题(is_topic=1),设置 expire_at
今日推荐覆盖手动指定某日的今日推荐内容(写入 daily_featured 表)
合集建议处理查看 collection_suggestions 列表,点击”接受/拒绝/暂缓”
异常队列查看被 AI 标记为疑似违规的内容,操作下线或恢复

不做(冷启动期不需要):内容批量上传 UI、A/B 测试、数据分析面板。

为什么只定义清单不定义 UI 细节:Admin 是内部工具,UI 由开发按最小可用原则实现,不需要产品规格级别的详细设计,清单足够开发评估工作量。


修复 8:新采集视频/音频的 channel 归属

Section titled “修复 8:新采集视频/音频的 channel 归属”

原问题:视频内容应归属哪个 channel 未定义,现有 5 个频道全是图文。

方案:新建两个 channel,写入数据库(channels 表),不修改代码,仅增加数据:

INSERT INTO channels (name, display_name, description, img_url, created_at, updated_at)
VALUES
('NHKWorldVideo', 'NHK World', 'NHK World 日语文化视频,由日本放送协会出品', '', NOW(), NOW()),
('ExternalVideo', '精选视频', '来自官方 YouTube 频道的精选日语视频内容', '', NOW(), NOW());

归属规则

  • NHK World YouTube 视频 → channel = NHKWorldVideo
  • 动漫官方 PV / JNTO 旅行视频 / 其他官方频道 → channel = ExternalVideo
  • NHK Radio / 播客 → channel = NHKWorldVideo(NHK 播客) 或 ExternalPodcast(其他播客)

为什么不用现有 NHK channel:现有 NHK channel 专指 NHK Web Easy 图文新闻,混入视频会导致现有频道列表 API 返回混合类型内容,影响现有功能。新建 channel 隔离类型,零破坏现有逻辑。


修复 9:内容看完后”下一步”行为定义

Section titled “修复 9:内容看完后”下一步”行为定义”

原问题:用户看完内容后 App 展示什么,是最核心的留存路径,完全未定义。

方案

场景下一步行为
内容属于某合集,且有下一集自动播放下一集(3秒倒计时动画,可点击取消)
内容属于某合集,已是最后一集展示”合集已看完”页面,推荐相同 scene 中类的其他合集
内容不属于任何合集展示相关推荐(同 primary_scene 的 3 条内容)
所有情况下底部固定展示”收藏”和”分享”按钮

为什么:参考 Netflix 的”下一集倒计时”逻辑——这是最强的连续消费引导机制,能将单次内容消费转化为合集追更行为,直接增加每次会话的内容消费量。3秒倒计时而不是立即跳转,给用户控制感。


原问题:用户点击锁定内容后看到什么,文档完全没有。

工程确认:iOS 已有完整付费墙实现(PaywallViewController / PaywallView / RevenueCat SDK),触发逻辑在 NewsDetailViewController+Paywall.swift 中——isMembershipRequired = true 时显示渐变遮罩 + 底部付费引导。

方案(直接复用现有实现)

  • 合集详情页的锁定条目:点击时触发 isMembershipRequired = true 的相同逻辑
  • 不需要新开发付费弹窗,直接复用 PaywallViewController 即可
  • 唯一需要定义的是锁定条目的列表 UI:显示锁图标(🔒)叠加在封面图右下角,标题正常展示(不隐藏,让用户知道有内容但需要付费)

为什么不隐藏内容:隐藏内容让用户看不到价值,没有付费动力。显示标题但锁定访问,让用户知道”有更多优质内容等着我”,这是驱动付费的关键心理机制(参考 Spotify 的播放列表锁定设计)。


原问题:服务器是 UTC,但产品面向日本用户,Streak 和 daily_featured 都应该按 JST 计算。

方案

数据库层:所有涉及”今日”的查询,统一使用:

-- 获取当前 JST 日期
DATE(CONVERT_TZ(NOW(), 'UTC', 'Asia/Tokyo'))
-- daily_featured 查询改为:
SELECT news_id FROM daily_featured
WHERE date = DATE(CONVERT_TZ(NOW(), 'UTC', 'Asia/Tokyo'))

Go 服务端层:定时任务使用 Tokyo 时区创建 cron:

loc, _ := time.LoadLocation("Asia/Tokyo")
c := cron.New(cron.WithLocation(loc))
c.AddFunc("0 6 * * *", generateDailyFeatured) // JST 06:00

Streak 判定:在服务端计算时,将用户的学习时长记录按 JST 日期分组,而不是按 UTC 日期分组。user_daily_learning 表的 date 字段存储 JST 日期字符串(YYYY-MM-DD)。

新增学习时长记录表

CREATE TABLE user_daily_learning (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
date DATE NOT NULL COMMENT 'JST 日期 YYYY-MM-DD',
duration_sec INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当日累计有效学习秒数',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_date (user_id, date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

为什么:用户在日本凌晨 23:59 看了 15 分钟,如果按 UTC 算已经是”明天”(UTC 14:59),Streak 会断。JST 统一保证日本用户的使用体验一致。


原问题:内容只能从首页进入,大量内容被埋没。

方案(最小可用版本,不过度设计)

立即可做(零新增工程量)

  • 详情页 → 所属合集入口:新闻详情页底部增加”收录于合集:XX”的入口(服务端返回内容时,附带 collection_id + collection_title,需新增 API 字段)
  • 内容标签可点击:点击场景标签(如”动漫”),跳转到该场景的内容列表页(现有 JLPT 专区的实现逻辑可复用)

延后到 P1

  • 全局搜索:按标题搜索内容
  • 分类筛选页:按场景/难度/类型多维筛选

为什么暂不做搜索:冷启动期内容总量少,搜索价值不大,且实现成本高。标签可点击是成本最低的内容发现扩展方式。


修复 13:重复内容检测改用 video_id

Section titled “修复 13:重复内容检测改用 video_id”

原问题:用 URL MD5 去重,但 YouTube URL 有多种格式(短链/完整链/带参数),同一视频的不同 URL 会被当做不同内容。

方案

// YouTube URL 规范化,提取 video_id
func extractYouTubeVideoID(url string) string {
// 支持格式:
// https://www.youtube.com/watch?v=VIDEO_ID
// https://youtu.be/VIDEO_ID
// https://www.youtube.com/embed/VIDEO_ID
// https://www.youtube.com/shorts/VIDEO_ID
}
// 去重字段:news.news_id 存储规范化后的来源唯一 ID
// YouTube: "youtube_" + video_id
// Podcast: "podcast_" + podcast_guid
// NHK: 现有逻辑不变(已用文章 URL 哈希)

news.news_id 字段已存在(现有逻辑已在用),只需调整 YouTube 内容入库时的 news_id 生成规则,从 URL MD5 改为 "youtube_" + video_id


原问题:合集级 is_membership 和条目级 is_free 冲突时优先级未定义。

规则(定死)is_free(条目级)优先级高于 is_membership(合集级)。

合集 is_membership条目 is_free用户访问结果
0(免费)1(免费)✅ 可访问
0(免费)0(付费)🔒 需会员
1(付费)1(免费)✅ 可访问(条目级覆盖)
1(付费)0(付费)🔒 需会员

为什么 is_free 优先:允许在付费合集里放几条免费内容作为”试看”,这是标准的付费产品转化设计(先让用户尝到甜头)。反之则无法实现试看功能。


原问题:无任何转录配额控制,冷启动期大量视频可能产生高额 Azure 费用。

方案

# 配置项(在服务配置中新增)
content_import:
whisper_daily_quota_minutes: 60 # 每日最多转录 60 分钟音视频(约 30 条5分钟视频)
whisper_priority_scenes: # 优先转录的 scene 中类
- 旅游
- 动漫
- 备考

控制逻辑

  1. 每日 00:00 JST 重置 whisper_used_minutes 计数器(存 Redis)
  2. 每次触发转录前检查:used_minutes + video_duration > daily_quota → 跳过,标注”转录配额已满”
  3. 优先转录 primary_scene 在白名单中的内容(旅游/动漫/备考先转)

为什么设 60 分钟/日:Azure Speech 约 $1/小时,60分钟/日 = $30/月,处于可接受的冷启动期成本范围。达到 500 条视频后再根据实际消耗调整。


原问题:Banner 轮播有 UI 定义但无数据结构。

工程确认:查阅代码,现有 FeaturedApplication 使用配置文件驱动(config.FeaturedConfig),Banner 尚无独立表或 API。

方案(最简化):复用现有配置机制,在服务端 config 中新增:

type BannerConfig struct {
Banners []BannerItem `json:"banners"`
}
type BannerItem struct {
ImageURL string `json:"image_url"`
LinkType string `json:"link_type"` // "collection" | "topic" | "paywall" | "url"
LinkID string `json:"link_id"` // collection_id / topic_id / 外部URL
ExpireAt string `json:"expire_at"` // "2026-04-30" 或空字符串(永不过期)
}

更新方式:通过环境变量/配置服务更新,无需 Admin UI(冷启动期运营通过修改配置文件更新 Banner)。

为什么不建 banners 表:冷启动期 Banner 更新频率极低(每两周一次),配置文件方式零工程成本,等 Admin 后台建设完成后再迁移到数据库管理。


参考主流音视频产品(Spotify / YouTube Music / 播客类 App)的播放模式设计,结合内容合集的消费场景,定义三种播放模式。

优先级说明

  • Phase 1(立即实现):单集播放(用户手动控制)
  • Phase 2(延后):合集顺序播放 + 单集循环
  • Phase 3(更晚):频道随机播放

模式名称行为图标建议
顺序播放合集连播当前内容结束 → 自动播放合集内下一条(按合集排序)▶▶
单集循环单集重复当前内容结束 → 重新播放当前内容🔂
不循环单集播放当前内容结束 → 停止,展示”下一集”入口(用户手动点)

:不做”随机播放”(shuffle)——日语学习内容有学习顺序逻辑,随机不适合。


只实现”单集播放(不循环)“模式

参数
默认模式单集播放(不循环)
内容播放结束后停止播放,屏幕停留在结束帧
底部展示”下一集:[标题]” 入口卡片(属于合集时展示)
无下一集时展示”合集已看完 ✓” + “相关推荐” 3 条
不属于任何合集时”相关推荐” 3 条(同 primary_scene 的内容)

Phase 1 不需要任何播放模式切换 UI,行为固定,简单可靠。


B.4 Phase 2 实现规格(延后,内容量 ≥ 100 条后)

Section titled “B.4 Phase 2 实现规格(延后,内容量 ≥ 100 条后)”

新增播放模式切换入口

播放器控制栏右侧增加一个模式切换按钮,点击循环切换三种模式:

不循环(▶)→ 单集循环(🔂)→ 合集连播(▶▶)→ 不循环(▶)→ ...

用户设置持久化

  • 存储字段:users.playback_mode(enum: none / single / collection,默认 none
  • 下次打开 App 记住上次选择
  • 游客不持久化,默认 none

合集连播的具体行为

  • 播完当前集 → 3 秒倒计时动画(显示”即将播放:[下一集标题]”)
  • 3 秒内用户可点击取消,取消后停留在结束页
  • 3 秒后自动跳转下一集,无需用户操作
  • 播到最后一集结束后 → 停止,展示”合集已看完 ✓”

单集循环的具体行为

  • 播完当前集 → 无停顿,立即重新播放
  • 无任何提示动画(符合循环播放的预期体感)
  • 不适合视频内容(视频循环体验差);主要对音频/播客有用

B.5 播放模式与内容类型的适用关系

Section titled “B.5 播放模式与内容类型的适用关系”
内容类型不循环单集循环合集连播
视频(YouTube)⚠️ 可用但体验一般
音频(播客)✅ 跟读/精听场景很有用
图文(NHK 新闻)❌ 文章循环无意义

Phase 1 说明:只做”不循环”模式,所有类型行为一致。


B.6 user_playback_state 表(Phase 2 新增)

Section titled “B.6 user_playback_state 表(Phase 2 新增)”
CREATE TABLE user_playback_state (
user_id BIGINT UNSIGNED PRIMARY KEY,
playback_mode ENUM('none','single','collection') NOT NULL DEFAULT 'none'
COMMENT 'none=单集不循环; single=单集循环; collection=合集顺序连播',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

附录B 创建:2026-03-28 | Phase 1 为当前实施目标,Phase 2 延后至视频内容 ≥ 100 条后


附录C:第二轮深度排查——实施卡点全消除

Section titled “附录C:第二轮深度排查——实施卡点全消除”

本轮从数据流、API 接口、客户端行为、运维监控四个维度穿透排查,消灭剩余卡点。


卡点 C-1:合集功能对 iOS 的 API 接口变更影响评估

Section titled “卡点 C-1:合集功能对 iOS 的 API 接口变更影响评估”

问题:iOS 当前调用的 API 完全没有合集相关接口,新增合集功能需要的 API 全部是新增的。需要定义清楚,否则后端和 iOS 联调时才发现不对齐。

当前 API 现状(确认已有):

  • GET /v1/featured → 返回频道 + 每个频道下的新闻列表(FeaturedResponse
  • GET /v1/news → 按 channel 分页返回新闻列表
  • GET /v1/news/:id → 新闻详情

合集功能需要新增的 API

GET /v1/collections
→ 返回首页展示的合集列表(is_visible=1,按 sort_order 降序)
→ 支持 ?type=curated|rolling_feed|series 筛选
→ 支持 ?channel=动漫日剧 筛选
Response: [{ id, title, description, cover_url, collection_type, level,
is_membership, is_finished, item_count }]
GET /v1/collections/:id
→ 合集详情(基础信息 + 内容列表)
→ content_list 按合集类型排序(rolling_feed: added_at DESC; curated: sort_position ASC; series: added_at ASC)
→ 每条内容携带 is_free 字段(控制前端是否显示锁)
→ 若用户已登录,附带 user_progress(watched_count, last_news_id)
Response: { collection, items: [{ news, is_free, sort_position, priority_score }], user_progress? }
GET /v1/news/:id/collection
→ 返回该条内容所属的合集信息(用于详情页底部"收录于合集"入口)
→ 若内容属于多个合集,返回 priority_score 最高的那个
Response: { collection_id, collection_title, collection_cover_url, item_position, total_items }
POST /v1/collections/:id/progress (需要登录)
→ 更新用户合集观看进度
Body: { last_news_id }
→ 自动计算 watched_count(数该 news_id 在合集内的 position)
Response: { watched_count, total_items }

Admin API 需要新增的

GET /admin/v1/collections → 合集列表
POST /admin/v1/collections → 创建合集
PUT /admin/v1/collections/:id → 编辑合集
POST /admin/v1/collections/:id/items → 添加内容到合集
DELETE /admin/v1/collections/:id/items/:news_id → 移除内容
PUT /admin/v1/collections/:id/items/reorder → 调整合集内排序(接受 [{news_id, sort_position}] 数组)
PATCH /admin/v1/collections/:id/visibility → 切换显示/隐藏
GET /admin/v1/collection-suggestions → 合集归档建议列表
PATCH /admin/v1/collection-suggestions/:id → 处理建议(accepted/rejected/deferred)

卡点 C-2:GET /v1/featured 的改造方式(不破坏现有 iOS)

Section titled “卡点 C-2:GET /v1/featured 的改造方式(不破坏现有 iOS)”

问题:现有首页用 GET /v1/featured 返回频道列表。新首页需要合集、每日资讯 Tab、JLPT 专区等多个模块,不可能都塞进 featured 这一个接口。iOS 需要知道调用哪些接口来组装首页。

方案:新增 GET /v2/featured,旧接口保持不变,iOS 新首页调新接口。

GET /v2/featured
Response:
{
"banner": [{ image_url, link_type, link_id, expire_at }], // 3条
"daily_featured": { news }, // 今日推荐(1条)
"collections": [{ collection基础信息 }], // 合集列表(is_visible=1的全部,前端取前8个展示)
"jlpt_counts": { "N5": 312, "N4": 421, "N3": 834, "N2": 512, "N1": 298 }, // 各级别内容数(用于 JLPT 专区按钮)
"quick_start": [{ news }] // 轻松入门4条(N5,固定)
}

为什么一次返回多个模块:首页冷启动需要多个模块的数据,多次请求会导致首屏白屏时间过长。一次请求返回所有首页必要数据,iOS 本地并行渲染各模块,体验最优。

每日资讯 Tab 数据:不放在 featured 接口里(数据量大,且用户只看一个 Tab),单独调用 GET /v2/news?tab=社会&limit=6&offset=0,Tab 切换时按需请求。


卡点 C-3:今日学习时长的客户端上报机制

Section titled “卡点 C-3:今日学习时长的客户端上报机制”

问题:15 分钟打卡目标需要客户端统计并上报时长,但上报接口和触发时机未定义。

方案

客户端统计

  • 进入视频/音频播放时,开始计时(local timer)
  • 退到后台或离开内容页时,停止计时,累计本次时长
  • 文章页:viewDidAppear 开始,viewWillDisappear 停止,最多计 5 分钟/篇

上报接口

POST /v2/learning/report (需要登录)
Body: { date: "2026-03-28", duration_sec: 180 }
→ 服务端写入 user_daily_learning 表(UPSERT,累加 duration_sec)
→ 当累计达到 900 秒(15分钟)时,自动触发 Streak +1 逻辑
Response: { today_duration_sec, streak_days, is_checked_in }

上报时机

  1. App 进后台时(scenePhase == .background)上报本次累计
  2. 退出内容页时上报
  3. App 每 5 分钟静默上报一次(防止 App 闪退丢失进度)

为什么不在内容播完时才上报:用户可能看一半就退出,进度不应丢失。每 5 分钟 + 退出时上报,确保进度实时准确。


卡点 C-4:Streak 服务端逻辑完整定义

Section titled “卡点 C-4:Streak 服务端逻辑完整定义”

问题:Streak 涉及用户每日学习的核心激励机制,服务端逻辑只有零散描述,没有完整定义。

完整逻辑

-- user_streaks 表(新增)
CREATE TABLE user_streaks (
user_id BIGINT UNSIGNED PRIMARY KEY,
current_streak INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前连续天数',
longest_streak INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '历史最长连续天数',
last_checkin_date DATE NULL COMMENT '上次打卡的 JST 日期',
revival_used_at DATE NULL COMMENT '最近一次使用复活的日期(每30天限1次)',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Streak 计算逻辑(在 POST /v2/learning/report 返回时触发):

today = CURRENT_JST_DATE
last = user_streaks.last_checkin_date
IF last == today:
→ 今天已打卡,不重复计算,直接返回 current_streak
IF last == today - 1 day:
→ 昨天打过卡,current_streak += 1
→ 更新 last_checkin_date = today
→ 如果 current_streak > longest_streak,更新 longest_streak
IF last < today - 1 day(或 last IS NULL):
→ 断连了,检查是否可以复活:
IF 付费用户 AND DATE_DIFF(today, last) <= 2 AND revival_used_at IS NULL OR DATE_DIFF(today, revival_used_at) > 30:
→ 可以触发复活(用户主动点击,不自动复活)
ELSE:
→ current_streak = 1(从今天重新开始)
→ 更新 last_checkin_date = today

API 接口

POST /v2/learning/streak/revive (需要付费会员)
→ 使用一次复活机会,补全昨天的打卡
→ 检查条件:距上次打卡 ≤ 2 天 + 本月复活次数 < 1
→ 写入 revival_used_at = today
Response: { current_streak, revival_remaining_this_month }
GET /v2/learning/streak
→ 返回当前 Streak 信息
Response: { current_streak, longest_streak, is_checked_in_today, revival_available }

卡点 C-5:内容流水线新增 Step 14 的实现位置

Section titled “卡点 C-5:内容流水线新增 Step 14 的实现位置”

问题:规格 C 附录修复 3 中定义了 rolling_feed 合集的 Step 14 自动追加逻辑,但在现有 MQ 消费者架构中,这个 Step 需要在哪里加?

确认位置:查阅 content_process_consumer.go,现有流水线中 messages.ContentProcessTypeAddScene 是最后一步。Step 14 应作为新的 MQ 消息类型加在 AddScene 之后:

// 在 messages 包中新增消息类型
const ContentProcessTypeAppendToCollections = "append_to_collections"
// content_process_consumer.go 中新增 case
case messages.ContentProcessTypeAppendToCollections:
err := c.collectionService.AppendNewsToMatchingCollections(ctx, e.UniqueID)
if err != nil {
log.Error()...
}

触发时机:AddScene 完成(打完场景标签)后,发送 ContentProcessTypeAppendToCollections 消息到同一队列。这样 Step 14 能在打完 scene 标签后运行,scene_id 已写入 news_scenes 表,合集规则中如果有 scene 过滤条件也能正确评估。


卡点 C-6:GET /v2/news 按 Tab 查询的服务端实现

Section titled “卡点 C-6:GET /v2/news 按 Tab 查询的服务端实现”

问题:每日资讯按 Tab 显示,需要服务端支持 ?tab=动漫 这种查询,但现有 GET /v1/news 只支持按 channel 过滤。

现有接口GET /v1/news?channel=NHK&offset=0&limit=20

新接口设计

GET /v2/news?tab=动漫&offset=0&limit=6&language=zh-Hans
Tab 参数到 scene_id 范围的映射在服务端处理:
"社会" → WHERE news_scenes.scene_id IN (49,50,51,52,57,58,59)
"娱乐" → WHERE news_scenes.scene_id IN (31,32,33,34,35,36,37,38)
"文化" → WHERE news_scenes.scene_id IN (11..26, 39..48)
"动漫" → WHERE news_scenes.scene_id IN (27,28,29,30)
"科技" → WHERE news_scenes.scene_id IN (53,54,55,56)
"旅行" → WHERE news_scenes.scene_id IN (15,16,17,18)
并且只返回 visibility IN (VISIBLE, MEMBERSHIP_VISIBLE) 且 scene_primary = 'primary' 的内容
排序:published_at DESC

注意:Tab 查询需要 JOIN news_scenes 表,比现有按 channel 查询多一个 JOIN,需要确保 news_scenes(scene_id, news_id) 有复合索引。

-- 确认这个索引是否存在(若不存在则新增)
ALTER TABLE news_scenes ADD INDEX idx_scene_primary (scene_id, scene_primary);

卡点 C-7:轻松入门区内容的选取查询

Section titled “卡点 C-7:轻松入门区内容的选取查询”

问题:轻松入门区展示 4 条 N5 内容(sentences 行数 ≤ 5),但”sentences 行数”需要子查询,现有 API 没有这个过滤参数。

方案:轻松入门区是运营手动维护的 4 条固定内容,不做动态查询。

实现方式:

  1. daily_featured 表中新增 quick_start 字段(JSON 数组存 4 个 news_id):
ALTER TABLE daily_featured ADD COLUMN quick_start_news_ids JSON NULL
COMMENT '轻松入门区4条内容的 news_id 数组,运营每月人工更新一次';
  1. 运营每月在 Admin 后台(或直接更新数据库)设置 4 个 N5 内容的 ID
  2. 服务端从这个字段读取并返回,不做动态筛选

为什么不动态查询:轻松入门区 30 天才更新一次,动态查询浪费资源。运营选内容本来就是一种质量保证(确保这 4 条真的适合入门用户),不应该完全自动化。


卡点 C-8:LLM 打标的 Prompt 在哪里存储、如何更新

Section titled “卡点 C-8:LLM 打标的 Prompt 在哪里存储、如何更新”

问题:LLM 打标 Prompt 规格已写清楚,但在工程里这个 Prompt 存在哪里?如何更新?不能硬编码,因为后续需要调整。

工程确认:已有 prompts 表和完整的 Admin 管理接口(GET/POST/PUT/PATCH /admin/v1/prompts)。Prompt 动态存储在数据库,可通过 Admin API 更新,无需改代码重新部署。

新增 Prompt 类型

INSERT INTO prompts (type, name, content, status, created_at, updated_at)
VALUES (
'content_intake_analysis',
'内容入库打标 Prompt v1',
'你是一个日语内容分类专家。根据以下内容信息,输出 JSON 格式的分类标签...[完整 Prompt 见规格第四章]',
'active',
NOW(), NOW()
);

调用方式:在新的打标服务中,通过 PromptsRepository.GetByType("content_intake_analysis") 动态读取 Prompt 内容,而不是硬编码。后续优化 Prompt 时只需通过 Admin 后台更新,立即生效,零停机。


卡点 C-9:JLPT 专区各级别的内容数量 API

Section titled “卡点 C-9:JLPT 专区各级别的内容数量 API”

问题:首页 JLPT 专区展示 5 个级别按钮,按钮上可能需要显示各级别内容数量(“N3 · 834条”),服务端需要提供这个统计数据。

方案:在 GET /v2/featured 的响应中,新增 jlpt_counts 字段:

"jlpt_counts": {
"N5": 312,
"N4": 421,
"N3": 834,
"N2": 512,
"N1": 298
}

服务端查询

SELECT level, COUNT(*) as cnt
FROM news
WHERE visibility IN ('VISIBLE', 'MEMBERSHIP_VISIBLE')
AND deleted_at IS NULL
AND level BETWEEN 1 AND 5
GROUP BY level;

缓存策略:这个统计数据 1 小时内不会有明显变化,缓存到 Redis(TTL = 3600 秒),避免每次请求都执行 COUNT 查询。


卡点 C-10:collection_items 表的唯一约束

Section titled “卡点 C-10:collection_items 表的唯一约束”

问题:如果同一条 news 被两次添加到同一个合集(重复操作),会产生重复条目,影响合集显示。collection_items 表缺少唯一约束。

修复

ALTER TABLE collection_items
ADD UNIQUE KEY uk_collection_news (collection_id, news_id);

应用层:Admin API 添加内容到合集时,使用 INSERT IGNOREON DUPLICATE KEY UPDATE sort_position=VALUES(sort_position),重复添加时更新排序位而不报错。


卡点 C-11:合集的 item_count 字段如何保持同步

Section titled “卡点 C-11:合集的 item_count 字段如何保持同步”

问题collections.item_count 是冗余字段(规格定义了),但没有说明它在哪里更新、如何保证与 collection_items 表同步。

方案

  1. 新增 collection_item 时:UPDATE collections SET item_count = item_count + 1 WHERE id = ?
  2. 删除 collection_item 时:UPDATE collections SET item_count = item_count - 1 WHERE id = ?
  3. 每日 02:00 JST 运行一次全量校正(防止异常导致计数偏移):
UPDATE collections c
SET c.item_count = (
SELECT COUNT(*) FROM collection_items ci WHERE ci.collection_id = c.id
);

卡点 C-12:视频/音频内容的 duration_sec 字段确认

Section titled “卡点 C-12:视频/音频内容的 duration_sec 字段确认”

问题:多处规格引用了 news.duration_sec 字段(如”时长 ≤ 1800 秒”,“时长 ≤ 300 秒”),但未确认此字段是否存在于 news 表。

确认结果:查阅 internal/domain/entity/news.goduration_sec 字段不存在于当前 news 表。图文内容无时长,但视频/音频内容需要。

修复

ALTER TABLE news ADD COLUMN duration_sec INT UNSIGNED NOT NULL DEFAULT 0
COMMENT '内容时长(秒),图文填0,视频/音频由导入时写入';

YouTubeImporter.buildContentInfo() 中,将 videoInfo.Duration 写入 news.duration_sec(该字段在 VideoInfo 中已有)。


卡点 C-13:内容无法正常播放时的降级方案

Section titled “卡点 C-13:内容无法正常播放时的降级方案”

问题:YouTube 视频可能因为各种原因无法播放(地区限制、版权下架、账号封禁),但规格没有定义这种情况下的用户体验。

方案

  1. iOS 播放器层:YouTube WebView 播放失败时(error callback),展示”该内容暂时无法播放”提示,不崩溃
  2. 服务端定期健康检查(每月一次,不影响冷启动期):检测视频链接是否仍可访问,不可访问的设置 visibility = ARCHIVED
  3. 冷启动期不做,等视频内容 ≥ 200 条后再考虑

卡点 C-14:新版首页的 A/B 测试或灰度发布策略

Section titled “卡点 C-14:新版首页的 A/B 测试或灰度发布策略”

问题:新首页(合集区、Tab 区等)是大改版,直接全量上线风险较高。是否需要灰度?

结论(定死):冷启动期不做 A/B 测试,新首页通过 App 版本号控制

  • 旧版 App(< v0.6.0):调用 GET /v1/featured,显示旧首页
  • 新版 App(≥ v0.6.0):调用 GET /v2/featured,显示新首页
  • 这和现有 X-App-Version header 的处理模式完全一致(已有 minVersionForPaidContent 的实现参考)

无需额外工程,直接沿用版本门控机制。


卡点 C-15:飞书通知的消息格式与接收账号配置

Section titled “卡点 C-15:飞书通知的消息格式与接收账号配置”

问题:规格多次提到”飞书通知 cc”,但没有说明通知发往哪个群/账号,使用哪个飞书 Bot,消息格式是什么。

工程确认:已有 feishu_notification_consumer.go,飞书通知基础设施完整。

配置项(补充到环境变量/config)

notification:
ops_feishu_chat_id: "oc_13ebca873dd4888b08ff34cb31f71f08" # 当前运营群
ops_alert_threshold: 3 # 异常队列超过3条时发告警

消息格式:复用现有飞书卡片消息格式(已有 feishu.SendCard 实现),不重新设计。


卡点 C-16:内容打标失败时的补打机制

Section titled “卡点 C-16:内容打标失败时的补打机制”

问题:LLM 打标偶尔会因 API 超时或输出不稳定而失败,失败的内容 primary_scene 为空,无法被 Tab 分类查询到(因为 JOIN news_scenes 时找不到记录)。这类内容会被永久埋没。

方案:新增补打机制:

-- 每日 01:00 JST 扫描打标缺失的内容(入库超过24小时但无 primary scene)
SELECT n.id
FROM news n
LEFT JOIN news_scenes ns ON n.id = ns.news_id AND ns.scene_primary = 'primary'
WHERE ns.news_id IS NULL
AND n.created_at < DATE_SUB(NOW(), INTERVAL 1 DAY)
AND n.deleted_at IS NULL
LIMIT 50;
-- 对每条记录,重新发送 ContentProcessTypeAddScene MQ 消息

这个定时任务已有天然的运行框架(cmd/scheduler),只需新增一个定时任务注册即可。


附录C 创建:2026-03-28 | 覆盖 API 设计、客户端上报、服务端 Streak、MQ 扩展、缓存策略等16个实施卡点


附录D:产品运营定义层排查——消灭所有运营执行模糊点

Section titled “附录D:产品运营定义层排查——消灭所有运营执行模糊点”

本附录专注于产品体验和内容运营层面的未定义问题。这些不是工程问题,而是”运营人员不知道该怎么做”或”用户体验会出现空洞”的问题。


D-1:新用户首次打开 App 的完整 Onboarding 流程

Section titled “D-1:新用户首次打开 App 的完整 Onboarding 流程”

问题:规格只定义了”注册时做一次兴趣选择”,但完整的新用户引导流程完全是空白。用户不知道这个 App 是干什么的、该怎么用。

完整 Onboarding 流程定义(共 4 步,不超过 60 秒):

Step 1:价值主张页(可跳过)
标题:「每天 15 分钟,沉浸式日语输入」
副标题:「不背单词,不做练习题,看视频和资讯就能学日语」
CTA:「开始使用」
跳过按钮:右上角「跳过」
Step 2:兴趣选择(必须完成,不可跳过)
标题:「你最想用日语做什么?」
选项(单选):
· 🎌 看懂动漫/日剧
· ✈️ 去日本旅行
· 📚 通过 JLPT 考试
· 🏯 了解日本文化
· 🎵 随便看看/保持语感
Step 3:难度选择(必须完成)
标题:「你的日语大概是什么水平?」
选项(单选):
· 完全零基础(对应 N5)
· 会五十音,学过一点(对应 N4)
· 能看懂简单新闻(对应 N3)
· 日常会话没问题(对应 N2)
· 想挑战专业内容(对应 N1)
Step 4:个人化首屏预览(可跳过)
根据选择展示「为你定制的内容预览」
CTA:「进入我的首页」

选择存储:Step 2 存 users.interest_category,Step 3 存 users.preferred_level(新增字段,N1-N5,冷启动期仅存储不消费)。

跳过逻辑:Step 2 和 3 若用户跳过,默认 interest_category = casualpreferred_level = N3


D-2:免费用户与付费用户的内容权益对比未定义

Section titled “D-2:免费用户与付费用户的内容权益对比未定义”

问题:文档里散落着”前 4 条免费,后 4 条会员专属”,但没有一个完整的权益对比表。用户不清楚”买会员能多获得什么”,运营也不知道如何设置付费墙。

权益对比表(定死)

内容类型免费用户会员用户
每日资讯(Tab 区)✅ 全部✅ 全部
NHK 今日要闻合集✅ 全部✅ 全部
日本娱乐速报合集✅ 全部✅ 全部
轻松入门区 4 条✅ 全部✅ 全部
日本文化冷知识合集✅ 前 3 条 🔒 其余✅ 全部
去日本旅行必备合集✅ 前 4 条 🔒 后 4 条✅ 全部
动漫日语精听合集✅ 前 3 条 🔒 其余✅ 全部
JLPT N3 冲刺合集✅ 图文(10条)🔒 视频✅ 全部
日语播客系列✅ 最新 2 期 🔒 历史✅ 全部
AI 解析功能❌ 不可用✅ 可用
Streak 复活❌ 不可用✅ 每月 1 次
无广告

原则:免费用户可以体验到所有合集的前几条,感受到内容质量,但精华内容锁定付费。每日资讯永远免费——这是 DAU 发动机,绝不设付费墙。


D-3:内容运营日历——每周/每月的固定运营工作

Section titled “D-3:内容运营日历——每周/每月的固定运营工作”

问题:规格定义了”每两周更换 Banner""每 30 天更换轻松入门区”,但没有完整的运营日历,运营人员不知道每周/每月要做什么。

运营日历(定死)

每日(自动,无需人工):

  • NHK Web Easy 日更内容自动入库
  • Yahoo 娱乐资讯自动入库
  • 09:00 JST:视频内容扩充追踪(仅异常时通知)

每周一(需人工处理):

  • 查收合集归档建议通知,决策是否创建新合集
  • 查收视频内容分布日报,判断哪个类别需要重点补充

每周五(需人工处理):

  • 查收热点内容采集建议,决定本周补充哪些主题
  • 发链接给 Captain 触发视频入库(本周采集计划执行)

每两周(需人工执行):

  • 更新首页 Banner(准备新的 3 张图片和跳转配置)
  • 评估当前热门专题效果,准备下一个专题的内容筛选

每月(需人工执行):

  • 更换轻松入门区 4 条内容(从 N5 内容中选 4 条最近的好内容)
  • 查收合集健康度报告,决定是否下线低效合集
  • 审视会员权益设置是否需要调整

JLPT 节点(特殊)

  • 考前 2 个月(5月/10月):上线「JLPT X月冲刺」专题,补充对应级别的题库类内容
  • 成绩公布后(8月/2月):更新专题为「下次备考计划」

D-4:内容质量标准——什么样的内容不该上首页

Section titled “D-4:内容质量标准——什么样的内容不该上首页”

问题:目前规格只定义了技术层面的”不上架条件”(时长过短/违规/重复),但没有定义运营层面的内容质量标准。运营人员不知道哪些内容该推哪些不该推。

内容质量评判标准(运营维度)

适合上合集/热门推荐的内容

  • 内容与日语学习有直接关联(不是纯娱乐新闻)
  • 视频清晰度 ≥ 480p
  • 字幕完整(或有完整文字稿)
  • 时长在 3–20 分钟之间(太短没学习价值,太长用户不完成)
  • 内容为 2024 年以后的(太旧的文化/新闻资讯失去时效性)

不推首页但保留在库的内容

  • 纯娱乐内容(无日语学习价值)
  • 仅英语配音/字幕(无日语学习属性)
  • 时长 < 3 分钟(放入每日资讯 Tab 而非合集)

直接下架的内容(自动过滤 + 运营确认):

  • 含政治敏感内容
  • 含明显广告/促销内容为主(内容 >50% 是广告)

D-5:合集的”完结”与”下线”如何运营

Section titled “D-5:合集的”完结”与”下线”如何运营”

问题collections.is_finished 字段代表合集完结,但没有定义什么情况下合集应该完结,完结后展示给用户是什么体验。

完结定义

场景是否完结展示文案
播客系列断更超过 4 周✅ 设为完结「已完结 · 共 X 期」
动漫某季度 PV 集合(季度结束)✅ 设为完结「2026春番精选 · 已完结」
JLPT 冲刺专题(考试过后)✅ 设为完结「7月场已结束」
日更合集(NHK/Yahoo)❌ 永不完结「持续更新中」

完结合集的首页展示规则

  • 已完结合集不出现在首页热门推荐中
  • 用户个人的”已看合集”历史中仍可见
  • 合集详情页顶部标注”已完结”灰色徽章

下线(is_visible = 0)规则

  • 合集健康度报告显示 30 天打开率 < 3% → 运营决策下线
  • 内容版权出现问题(收到下架要求)→ 立即下线
  • 下线 ≠ 删除,数据保留,随时可重新上线

问题:策划文档多次提到”分享率”是 KPI,但整个规格没有任何地方定义什么可以分享、分享出去是什么样子。

可分享的内容(定义)

分享类型分享载体分享内容
分享单条内容图片卡片内容封面 + 标题 + 难度标签 + App Logo + 二维码
分享今日学习成果图片卡片Streak 天数 + 今日学习时长 + 今日内容标题 + App Logo
分享合集文字 + 链接「我在学《去日本旅行必备》

分享卡片设计原则

  • 必须包含「Yomiya 每日日语」品牌
  • 包含二维码(指向 App Store 下载页)
  • 图片比例:9:16(适合微信/小红书故事发布)

什么时候触发分享入口

  • 内容详情页底部固定”分享”按钮
  • Streak 达到整数里程碑(7天/30天/100天)时弹出分享引导
  • 合集全部看完时展示分享引导

问题:没有任何地方定义 App 会发什么推送、什么时候发、发给谁。推送是最强的召回工具,不定义等于放弃。

推送策略(分类定义)

每日内容推送(所有用户,默认开启)

  • 时间:09:00 JST(用户可自定义时间,暂不做,默认09:00)
  • 内容:「今日推荐:[今日内容标题]」
  • 频率:每日1条,不可增加

Streak 相关推送

  • Streak 将断提醒:当日 21:00 JST,如果用户今日未达 15 分钟 → 发送「你今天还有 X 分钟就完成打卡了!」
  • Streak 里程碑祝贺:达到 7天/30天/100天 时 → 「🎉 连续学习 X 天了!」(一次性,不重复)
  • Streak 断连后召回:断连第 2 天 → 「X 天的连续记录别丢了,今天回来继续!」

内容更新推送(用户订阅的合集/系列)

  • 播客系列新一期上线 → 「[系列名] 更新了:[新一期标题]」
  • 仅对”已看过该系列 ≥ 2 期”的用户推送(降低骚扰)

推送频率上限:每日最多接收 2 条推送,超出则按优先级取前 2 条(优先级:Streak 断连 > 每日内容 > 系列更新)。

推送可关闭:用户可在设置中分类关闭推送(每日内容 / Streak 提醒 / 系列更新 分开控制)。


D-8:用户回归体验(超过 7 天未打开 App)

Section titled “D-8:用户回归体验(超过 7 天未打开 App)”

问题:Streak 断了的用户回来会看到什么?规格完全没有定义回归用户的体验,这批用户极其宝贵,需要特别设计。

回归用户场景与体验

离开 2–7 天(轻度流失)

  • 首屏正常显示,不做特殊处理
  • 今日推荐显示正常
  • Streak 显示已断(数字变灰),显示”继续新的 Streak”引导

离开 7–30 天(中度流失)

  • 首次进入时展示「欢迎回来」全屏弹窗
  • 内容:“上次你在学 [上次看的内容],这几天又有 [X] 条新内容等你”
  • CTA 按钮:「继续看」(直接跳到上次内容)和「看看新内容」(跳首页)

离开 30 天以上(重度流失)

  • 强制引导重新选择兴趣(提示”你的偏好设置需要更新”)
  • 走 Onboarding Step 2 和 Step 3 流程(跳过 Step 1)
  • 目的:重新激活用户兴趣,刷新个性化推荐(冷启动期虽然不启用个性化,但数据要记录)

问题:规格只说”AI 给出版权风险提示,人工最终确认”,但没有定义具体的版权操作 SOP。运营人员遇到版权问题不知道怎么处理。

版权风险分级与操作规范

风险等级判断依据操作
安全官方认证频道(NHK/JNTO/东宝官方等白名单)直接入库,无需审核
低风险来自已知媒体机构的公开视频,标注来源入库,标注版权来源,定期核查
中风险来源不明的视频、混剪类内容入库但标注 is_risk = true,不上首页,不放入合集,仅存档
高风险AI 判断含明显未授权内容、第三方转载拒绝入库

收到下架要求时的 SOP

  1. 立即将该内容 visibility = INVISIBLE(5分钟内执行)
  2. 从所有合集中移除该内容
  3. 记录下架原因和时间
  4. 不删除数据库记录(保留审计追踪)

YouTube 白名单维护:在服务端配置中维护可信频道 Handle 列表,白名单内的频道无需每次人工确认版权。白名单更新由 cc 决策,操作通过飞书发指令给 Captain 写入配置。


D-10:内容难度分级的准确性验证

Section titled “D-10:内容难度分级的准确性验证”

问题:LLM 自动判断难度(N5-N1),但这个判断准不准?没有验证机制,难度标签错误会严重影响用户体验(N1 内容标了 N3,初学者点进去完全看不懂)。

难度分级质量控制方案

冷启动期(内容 < 500 条时)

  • 每批新采集内容,人工抽检 10%(约 5 条/50 条新内容)验证难度是否正确
  • 发现系统性偏差(如 NHK World 视频普遍被标为 N2,实际是 N3)→ 修正 Prompt

冷启动期结束后

  • 用用户行为数据反向验证:某级别内容的完成率与该难度下的用户占比对比
  • 完成率远低于期望 → 内容可能被低估难度(标 N3 但实际是 N1)

快速人工修正通道:Admin 后台”内容难度修正”功能,运营可一键修改某条内容的 level,修改后自动重新触发 LLM 打标(确保其他标签与新难度一致)。


D-11:空状态设计(各模块内容不足时展示什么)

Section titled “D-11:空状态设计(各模块内容不足时展示什么)”

问题:多个首页模块在初期内容不足时不展示(如动漫 Tab 未达 30 条),但完全没有定义用户在”即将上线”阶段看到什么。

各模块空状态/未上线状态处理

模块内容不足时的展示
每日资讯 Tab(未达到条件)该 Tab 完全不显示(不展示空 Tab)
合集区(不足 8 个合集)展示已有合集(如 2 个),剩余位置展示”即将上线”占位卡片(灰色,写”更多合集即将到来”)
今日推荐(无当日内容)展示昨日最高完成率的1条内容,标注”今日内容加载中”
播客系列(未上线)不显示该模块,不展示占位
轻松入门(无 N5 内容)不显示该模块

关键原则:宁愿不展示,也不展示空壳。用户看到空壳比看不到更差。


D-12:内容消费的激励体系是否需要积分/勋章

Section titled “D-12:内容消费的激励体系是否需要积分/勋章”

问题:规格只有 Streak 一种留存激励,但市场上主流语言学习 App(Duolingo/Babbel)都有更丰富的激励体系。每日日语是否需要勋章/成就系统?

结论(定死,不在冷启动期做)

勋章/成就系统延后到 v1.0 以后,原因:

  1. 冷启动期内容不足,过早引入成就系统会因内容单一而失效
  2. 勋章系统工程量大,影响首页 MVP 上线时间
  3. 当前阶段只需 Streak + 会员权益两个激励杠杆

但以下数据必须在冷启动期开始统计(为未来勋章系统准备):

  • 用户累计消费内容总数
  • 用户消费过的 scene 中类数量(“涉猎广度”)
  • 用户在某个 scene 中类的累计消费量(“深度专精”)

问题:C-12 将搜索延后,但搜索入口放在哪里?什么时候上线?搜索结果页是什么样?这些都是 iOS 开发需要预留位置的。

搜索功能分阶段定义

Phase 1(预留入口,不实现)

  • 首页顶部预留搜索图标入口(灰色,点击展示”即将上线”Toast)
  • 目的:让用户知道将来会有搜索,不造成功能缺失的负面印象

Phase 2(延后,内容 ≥ 500 条后)

  • 支持按标题关键词搜索(日文/中文均可)
  • 搜索结果按相关度排序,显示内容类型标签
  • 搜索结果支持按 level/type 筛选

Phase 3(更晚)

  • 按场景标签浏览(“旅行日语”频道页)
  • 语音搜索

D-14:会员订阅后的服务承诺是否清晰

Section titled “D-14:会员订阅后的服务承诺是否清晰”

问题:付费墙存在,但用户在订阅前看到的”会员权益说明”是什么?如果权益说明不够清晰,转化率会很低。

会员权益展示定义(付费页必须包含以下内容):

权益清单(用图标+文字展示):

  • 🎬 解锁全部视频合集(含动漫/旅行/文化精选)
  • 🎧 收听完整播客档案(历史所有期数)
  • 🤖 AI 解析:搞懂每句话的文化背景
  • 🔥 Streak 复活:每月 1 次,守护连续记录
  • 📚 JLPT 冲刺视频:N3/N2/N1 高频词汇讲解

不做隐藏条款:权益页必须标注”免费用户可永久使用的内容”(今日资讯/每日推荐/基础合集预览),避免用户感觉”被骗订阅”。


问题:使用 NHK、Yahoo 等外部内容,App 内是否需要显示版权归属?不显示可能有法律风险。

版权标注规范

内容来源标注方式标注位置
NHK Web Easy「© NHK」内容详情页底部
NHK World YouTube「© NHK World」内容详情页底部
Yahoo! 娱乐新闻「© Yahoo! JAPAN」内容详情页底部
动漫官方 PV「© [制作公司]」(从 YouTube 频道信息中提取)内容详情页底部
平台原创内容「© Yomiya」不显示(默认)

内容详情页底部信息区

[内容标题]
[日期] · [来源名称] · [难度标签]
---
[正文]
---
© NHK · 内容来源于 NHK Web Easy,仅用于日语学习目的

附录D 创建:2026-03-28 | 产品运营定义层,15个卡点,覆盖 Onboarding、权益对比、运营日历、版权规范、通知策略等


文档拆分后索引(2026-03-28 新增)

Section titled “文档拆分后索引(2026-03-28 新增)”

为防止当前执行继续依赖这份持续膨胀的长文档,现补充拆分后索引如下:

  1. yomiya-phase1-execution-spec.md
  2. yomiya-canonical-naming.md
  3. yomiya-phase1-scope-boundary.md
  4. yomiya-implementation-spec-core.md
  1. yomiya-implementation-review-log.md
  2. yomiya-implementation-spec-split-plan.md
  1. yomiya-implementation-open-questions.md
  2. yomiya-implementation-future-phases.md

说明:

  • 本文档继续保留原始实施规格与历史扩展内容
  • 当前新增文档用于把“当前有效规格、历史问题、开放问题、未来阶段”分层
  • 后续如果继续拆分或迁移,以新增文档为主,本文件继续作为历史留档入口