日期:2026-09-19 状态:待审查 范围:cfc-backend + cfc-langgraph + cfc-frontend(小程序文章详情页)
用户读完文章(达到阅读时长)后,系统根据文章内容 + 用户画像 AI 生成若干道选择题。用户作答后,根据答对数量发放 CF 值 和 五维能量,并将题目与作答情况写入用户的 认知雷达(profile_snapshot 扩展字段),作为后续精准推送的基础。
| 环节 | 现状 | 差距 |
|---|---|---|
| 阅读计时/达标 | 前端计时 + /api/articles/report-reading-time + /api/articles/complete-read(+5能量) |
无 |
| AI 出题 | /api/articles/quiz/generate 调 aiService.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) 均可用 |
需接线 |
用户读完文章(前端计时达标)
│
├─ ① 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(认知雷达)
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。
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。
cfc-langgraph/app/graphs/article_quiz_graph.pyarticle_title、article_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.py 的 DEFAULT_PROMPTS 增加 article_quiz 条目)。
cfc-langgraph/app/api/article_quiz.pyPOST /api/v1/article/quiz/generate,Pydantic 模型放 app/models/article_quiz.py。{"questions": [...], "fallback_used": false}。app/main.py 注册 include_router(article_quiz.router)。AiGateway.generateArticleQuiz(articleTitle, articleContent, profileSummary):
baseUrl + "/api/v1/article/quiz/generate"null,由调用方降级。/api/articles/quiz/generate(ArticleCommentController)aiGateway.generateArticleQuiz(...),成功则取得题目列表。profile_summary:从 profile_snapshot 读取当前成员画像摘要传入 AI。article_quiz_records(answers 为 NULL),返回 {recordId, questions}。判分以库内答案为唯一依据,前端无法篡改。/api/articles/quiz/submit(ArticleCommentController)改造为完整落库 + 双奖励 + 画像更新:
recordId、memberId、answers(含每题所选选项)。recordId 读取库内题目快照,后端自行判分:计算 score(答对题数)、totalQuestions。article_quiz_records(作答 JSON + 得分 + 奖励额度),拒绝重复提交(answers 已非 NULL)。pointsService.awardCfPoints(memberId, cfEarned, "文章答题奖励")energyService.awardEnergy(memberId, "article_quiz", articleId, energyEarned, "文章答题奖励", null),按文章维度分配。profile_snapshot.quiz_summary(认知雷达汇总,见 4.2)。{correctCount, totalQuestions, cfEarned, energyEarned}。判分与现有
ArticleService.getAiQuestions/submitAnswers模式一致(出题建记录 → 提交按 recordId 读库判分),保持代码风格统一。
| 场景 | CF 值 | 能量 |
|---|---|---|
| 阅读完成 | 0 | +5(已存在) |
| 答题:每答对 1 题 | +1 | +5 |
| 答题:全对额外奖励 | +3 | 0 |
规则常量先硬编码在 Service 层,后续如需运营可配置,再迁移到
sys_config(YAGNI,本期不引入配置项)。
文件:pages/article-center/article-detail.vue
startQuiz:调用 generateQuiz 不变,响应结构仍为题目数组(后端已兼容)。submitQuiz:改为传「每题所选选项」而非 correctCount;结果展示改为同时显示 CF 值 + 能量。result-dialog:增加「CF 值 +X」展示。前端遵循:禁止可选链
?.、禁止 CSS Grid、:key用方法调用、日期用parseDate()。小程序打包由 HBuilderX 完成,Agent 只做语法校验。
DatabaseInitializer.runMigrations() 新增(最新迁移编号 298,递增):
article_quiz_records 表。profile_snapshot 增加 quiz_summary JSON 列(ensureColumn)。同步更新 schema.sql(建表语句 + profile_snapshot 列)。
article_quiz_records 已存在作答记录(answers_json 非空)时拒绝重复提交(与现有 submitAnswers 逻辑一致)。awardCfPoints 内部抛「孩子不存在」,提交接口需捕获并降级(能量/CF 失败不阻断答题结果返回)。dimension 缺省 wisdom,quiz_summary.by_dimension 使用 wisdom 兜底。mvn clean compile 验证编译通过(项目当前唯一验证方式)。uvicorn 起服务后 curl /api/v1/article/quiz/generate 验证返回 3 题结构化 JSON。node --check 校验修改的 script 块语法(不打包,HBuilderX 负责打包)。article_quiz_records、发放 CF 值 + 五维能量、更新 profile_snapshot.quiz_summary。mvn clean compile 通过。