2026-09-19-article-reading-quiz-design.md 9.2 KB

文章阅读后答题奖励 + 认知雷达记录 — 设计规格

日期:2026-09-19 状态:待审查 范围:cfc-backend + cfc-langgraph + cfc-frontend(小程序文章详情页)

1. 需求概述

用户读完文章(达到阅读时长)后,系统根据文章内容 + 用户画像 AI 生成若干道选择题。用户作答后,根据答对数量发放 CF 值五维能量,并将题目与作答情况写入用户的 认知雷达profile_snapshot 扩展字段),作为后续精准推送的基础。

2. 现状盘点

环节 现状 差距
阅读计时/达标 前端计时 + /api/articles/report-reading-time + /api/articles/complete-read(+5能量)
AI 出题 /api/articles/quiz/generateaiService.sendWorkflow("article-quiz")(Dify),失败回退 3 道硬编码题 未走 AiGateway + LangGraph
提交答题 /api/articles/quiz/submit 只算分、发能量(答对×5),未持久化、未发 CF 值、未写画像 缺记录表、缺 CF、缺画像
记录表 ArticleQuizRecord 实体存在(@TableName("article_quiz_records")),但 schema.sql 无建表语句 需建表
认知雷达 profile_snapshot 表存在(含 dimension_scores/problem_domains JSON),无答题维度 需扩展
奖励服务 PointsService.awardCfPoints(memberId, amount, reason)EnergyService.awardEnergy(memberId, sourceType, sourceId, totalAmount, desc, daysToExpire) 均可用 需接线

3. 架构与数据流

用户读完文章(前端计时达标)
        │
        ├─ ① POST /api/articles/complete-read   → 发阅读能量(已存在)
        │
        ├─ ② POST /api/articles/quiz/generate   → AiGateway.generateArticleQuiz()
        │        └─ LangGraph /api/v1/article/quiz/generate(article_quiz_graph)
        │              失败 → 降级到现有硬编码 3 题 fallback
        │
        ├─ ③ 用户作答(前端 quiz 弹窗)
        │
        └─ ④ POST /api/articles/quiz/submit
                 ├─ 落库 article_quiz_records(题目+作答+得分)
                 ├─ 发 CF 值(PointsService.awardCfPoints)
                 ├─ 发五维能量(EnergyService.awardEnergy,按文章关联维度分配)
                 └─ 更新 profile_snapshot.quiz_summary(认知雷达)

4. 数据模型

4.1 新增表 article_quiz_records

实体 ArticleQuizRecord 已存在,建表时采用规范字段(表尚不存在,无需历史兼容,直接建最终结构):

字段 类型 说明
id BIGINT PK AI 主键
article_id BIGINT 文章 ID
user_id BIGINT 登录用户 ID
member_id BIGINT 家庭成员 ID(作答人,统一口径,见 docs/architecture/userid-vs-memberid-usage-guide.md
questions TEXT 题目快照 JSON(含正确答案与维度)
answers TEXT 用户作答 JSON(含每题所选选项),作答前为 NULL
score INT 答对题数
total_questions INT 题目总数
cf_earned INT 发放的 CF 值
energy_earned INT 发放的能量值
created_at DATETIME 创建时间
updated_at DATETIME 更新时间

实体现有 childId/pointsEarned 字段不再使用(表为全新创建,不引入 childId 口径);questions/answers 字段直接承载 JSON。实体需同步调整:去掉 childId/pointsEarned,新增 memberId/totalQuestions/cfEarned/energyEarned

4.2 扩展 profile_snapshot

新增一列:

字段 类型 说明
quiz_summary JSON 答题认知雷达汇总:{"quiz_count":N,"correct_rate":0.8,"by_dimension":{"wisdom":{"quiz_count":n,"correct_rate":r},...},"last_quiz_at":"..."}

by_dimension 的 key 使用五维代码(body/mind/wisdom/action/wealth),来自文章的 related_dimensions / dimension_weights

5. AI 出题(LangGraph)

5.1 新 graph:cfc-langgraph/app/graphs/article_quiz_graph.py

  • 输入:article_titlearticle_content(截断至合理长度)、profile_summary(用户画像摘要,可选)
  • 输出:3 道单选题,结构化 JSON:

    [
    {
    "question": "问题文本",
    "options": ["A. xxx", "B. xxx", "C. xxx", "D. xxx"],
    "answer": "A",
    "dimension": "wisdom",
    "explanation": "解析文本(可选)"
    }
    ]
    
  • dimension 取值与文章 related_dimensions 对齐,缺省为 wisdom

  • 系统提示词走 prompt_service.get_prompt("article_quiz"),无则用内置 DEFAULT_PROMPTS 兜底(需在 prompt_service.pyDEFAULT_PROMPTS 增加 article_quiz 条目)。

5.2 新路由:cfc-langgraph/app/api/article_quiz.py

  • POST /api/v1/article/quiz/generate,Pydantic 模型放 app/models/article_quiz.py
  • 响应:{"questions": [...], "fallback_used": false}
  • app/main.py 注册 include_router(article_quiz.router)

5.3 AiGateway 新方法

AiGateway.generateArticleQuiz(articleTitle, articleContent, profileSummary)

  • baseUrl + "/api/v1/article/quiz/generate"
  • 遵循现有熔断/超时/Fallback 模式,失败返回 null,由调用方降级。

6. 后端接口改造

6.1 /api/articles/quiz/generate(ArticleCommentController)

  • 改为:先调 aiGateway.generateArticleQuiz(...),成功则取得题目列表。
  • 失败则降级到现有硬编码 3 题 fallback(保持不变)。
  • 组装 profile_summary:从 profile_snapshot 读取当前成员画像摘要传入 AI。
  • 建记录并返回 recordId:将题目快照(含正确答案)落库到 article_quiz_recordsanswers 为 NULL),返回 {recordId, questions}。判分以库内答案为唯一依据,前端无法篡改。

6.2 /api/articles/quiz/submit(ArticleCommentController)

改造为完整落库 + 双奖励 + 画像更新:

  1. 接收 recordIdmemberIdanswers(含每题所选选项)。
  2. recordId 读取库内题目快照,后端自行判分:计算 score(答对题数)、totalQuestions
  3. 回填 article_quiz_records(作答 JSON + 得分 + 奖励额度),拒绝重复提交(answers 已非 NULL)。
  4. 发放奖励:
    • CF 值pointsService.awardCfPoints(memberId, cfEarned, "文章答题奖励")
    • 五维能量energyService.awardEnergy(memberId, "article_quiz", articleId, energyEarned, "文章答题奖励", null),按文章维度分配。
  5. 更新 profile_snapshot.quiz_summary(认知雷达汇总,见 4.2)。
  6. 返回 {correctCount, totalQuestions, cfEarned, energyEarned}

判分与现有 ArticleService.getAiQuestions/submitAnswers 模式一致(出题建记录 → 提交按 recordId 读库判分),保持代码风格统一。

6.3 奖励规则(可配置)

场景 CF 值 能量
阅读完成 0 +5(已存在)
答题:每答对 1 题 +1 +5
答题:全对额外奖励 +3 0

规则常量先硬编码在 Service 层,后续如需运营可配置,再迁移到 sys_config(YAGNI,本期不引入配置项)。

7. 前端改造(cfc-frontend)

文件:pages/article-center/article-detail.vue

  • startQuiz:调用 generateQuiz 不变,响应结构仍为题目数组(后端已兼容)。
  • submitQuiz:改为传「每题所选选项」而非 correctCount;结果展示改为同时显示 CF 值 + 能量。
  • 结果弹窗 result-dialog:增加「CF 值 +X」展示。

前端遵循:禁止可选链 ?.、禁止 CSS Grid、:key 用方法调用、日期用 parseDate()。小程序打包由 HBuilderX 完成,Agent 只做语法校验。

8. 迁移清单

DatabaseInitializer.runMigrations() 新增(最新迁移编号 298,递增):

  • 迁移 299:创建 article_quiz_records 表。
  • 迁移 300profile_snapshot 增加 quiz_summary JSON 列(ensureColumn)。

同步更新 schema.sql(建表语句 + profile_snapshot 列)。

9. 错误处理与边界

  • AI 出题失败:降级到硬编码 fallback,不影响答题主流程。
  • 重复提交article_quiz_records 已存在作答记录(answers_json 非空)时拒绝重复提交(与现有 submitAnswers 逻辑一致)。
  • 成员不存在awardCfPoints 内部抛「孩子不存在」,提交接口需捕获并降级(能量/CF 失败不阻断答题结果返回)。
  • 文章无维度dimension 缺省 wisdomquiz_summary.by_dimension 使用 wisdom 兜底。

10. 测试

  • 后端:mvn clean compile 验证编译通过(项目当前唯一验证方式)。
  • LangGraph:本地 uvicorn 起服务后 curl /api/v1/article/quiz/generate 验证返回 3 题结构化 JSON。
  • 前端:node --check 校验修改的 script 块语法(不打包,HBuilderX 负责打包)。

11. 成功标准

  • 阅读达标后弹出 AI 出题的答题弹窗(AI 失败时 fallback 3 题)。
  • 提交答题后:落库 article_quiz_records、发放 CF 值 + 五维能量、更新 profile_snapshot.quiz_summary
  • 前端结果弹窗展示「答对 X/Y 题」「+CF 值」「+能量」。
  • mvn clean compile 通过。