Phase 1.1 (DB迁移) ──→ 1.3 (createDynamicTask) ──→ 1.4 (Controller改造)
│
Phase 1.2 (TaskParseService) ──────────────────────────┘
│
Phase 1.8 (FamilyContext增强) ─────────────────────────┘
↓
1.9 (编译验证)
│
┌───────────────────────────────────────┘
↓
Phase 1.5 (AITaskCard组件) ──→ 1.6 (chat.vue改造) ──→ 1.7 (报告详情页入口)
│
↓
Phase 2.x (持续增强)
目标: 支持 AI_GENERATED 类型任务,新增维度跟踪字段
文件:
cfc-backend/src/main/resources/schema.sql — 同步建表语句cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java — 迁移代码cfc-backend/src/main/java/com/etotem/cfc/entity/GrowthTask.java — 新增字段迁移内容:
-- migration N: growth_tasks 支持 AI 生成任务
ALTER TABLE growth_tasks MODIFY COLUMN type
ENUM('DAILY','NEWBIE','AI_GENERATED') NOT NULL DEFAULT 'DAILY';
ALTER TABLE growth_tasks ADD COLUMN dimension VARCHAR(20) NULL COMMENT '五维维度';
ALTER TABLE growth_tasks ADD COLUMN source_conversation_id VARCHAR(100) NULL COMMENT '来源AI会话ID';
实体类新增字段:
@TableField("dimension")
private String dimension;
@TableField("source_conversation_id")
private String sourceConversationId;
验收: mvn clean compile 通过,迁移可重复执行不报错
目标: 新建服务解析 Dify 返回的 [TASK:{...}] 标记,提取任务参数
文件: 新建 cfc-backend/src/main/java/com/etotem/cfc/service/TaskParseService.java
1. TASK: 解析 Dify answer 中的 [TASK:{"title":"...","description":"...",...}] 标记
2. EXPECTED OUTCOME: 返回解析后的 TaskParseResult 对象列表
3. REQUIRED TOOLS: 正则 Pattern + Jackson ObjectMapper
4. MUST DO:
- 正则 `\[TASK:\s*(\{.*?\})\](?:\s*\[TASK:...)*` 提取所有 TASK 标记
- 解析 JSON → TaskParseResult 对象
- 校验 title 必填
- 返回解析结果列表
- 从原 answer 中移除所有 TASK 标记
5. MUST NOT DO: 不写入数据库(由调用方控制)
TaskParseResult DTO:
public class TaskParseResult {
private String title; // 必填
private String description; // 选填
private String deadline; // 选填 yyyy-MM-dd
private String dimension; // 选填 body/mind/wisdom/action/wealth
private Integer rewardPoints; // 选填
}
验收: "[TASK:{\"title\":\"读书30分钟\",\"dimension\":\"wisdom\"}]" → 解析出 title=读书30分钟, dimension=wisdom
目标: 新增方法,根据 AI 生成的任务参数创建 growth_tasks + growth_task_log
文件: cfc-backend/src/main/java/com/etotem/cfc/service/GrowthTaskService.java
新增方法:
/**
* 创建 AI 动态生成任务
* @param userId 目标用户ID
* @param task 解析后的任务参数
* @param conversationId 来源AI会话ID
* @return 创建的任务ID
*/
@Transactional
public Long createDynamicTask(Long userId, TaskParseResult task, String conversationId) {
// 1. 插入 growth_tasks (type=AI_GENERATED, enabled=1)
// 2. 插入 growth_task_log (progress=0, completed=0, claimed=0)
// 3. 返回 taskId
}
验收: 调用后 growth_tasks 和 growth_task_log 各多一条记录
目标: 统一处理 [RECOMMEND] 和 [TASK] 标记,全模式支持推荐
文件: cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java
改动点:
sendMessage() 方法改造:
parseAndHandleMarkers(answer){ answer, conversationId, recommendations, tasks }parseAndHandleMarkers() 新增方法:
private Map<String, Object> parseAndHandleMarkers(String answer, Long userId, String conversationId) {
// 1. 解析 [RECOMMEND:...] → RecommendationService.search
// 2. 解析 [TASK:...] → TaskParseService → GrowthTaskService.createDynamicTask
// 3. 返回 { cleanAnswer, recommendations, tasks }
}
sendNutritionMessage() 简化: 复用 parseAndHandleMarkers() 避免重复
验收:
目标: 可复用的任务卡片组件,展示任务信息 + 接受按钮
文件: 新建 cfc-frontend/components/AITaskCard.vue
<template>
<view class="ai-task-card">
<view class="task-header">
<text class="task-icon">📋</text>
<text class="task-title">{{ task.title }}</text>
</view>
<text class="task-desc" v-if="task.description">{{ task.description }}</text>
<view class="task-footer">
<text class="task-dimension" v-if="task.dimension">维度:{{ dimLabel }}</text>
<text class="task-deadline" v-if="task.deadline">截止:{{ task.deadline }}</text>
</view>
<button class="task-accept-btn" :loading="accepting" @click="handleAccept">接受任务</button>
</view>
</template>
<script>
export default {
props: {
task: Object,
conversationId: String
},
data() { return { accepting: false } },
methods: {
async handleAccept() {
// 调用 POST /api/tasks/accept-dynamic
// 参数: { taskTitle, taskDescription, deadline, dimension, conversationId }
this.accepting = true
try {
const res = await api.acceptDynamicTask({...})
if (res.code === 200) {
uni.showToast({ title: '任务已接受' })
this.$emit('accepted', res.data)
}
} finally {
this.accepting = false
}
}
}
}
</script>
验收: 组件在 chat.vue 中导入后可正常渲染和点击接受
目标: 支持任务卡片渲染、全模式推荐展示、报告解读模式
文件: cfc-frontend/pages/ai/chat.vue
改动点:
mode=report — 接收 reportId 参数,sendMessage 时自动传入消息气泡标注来源 — 如果是报告解读,气泡上显示"基于健康报告"标签
改动区段:
- <template> 中 #80~90 行,推荐卡片区域改为无条件渲染
- <template> 消息气泡内新增任务卡片区域
- <script> data() 中新增支持任务字段
- <script> sendMessage() 中参数扩展:reportId
- <script> onLoad() 中新增 mode=report 处理
验收:
chat.vue?mode=report&reportId=123 → 发消息时自动带 reportId目标: 各报告详情页底部增加"让浠宝/福宝解读"入口
文件:
cfc-frontend/pages/body-detail/health-report.vuecfc-frontend/pages/health/gut-index.vuecfc-frontend/pages/health/tongue-index.vuecfc-frontend/pages/health/report-upload.vuecfc-frontend/pages/body-detail/health-report.vue实现方式: 各页面的模板底部新增:
<view class="ai-interpret-entry" @click="goAIChat">
<image class="ai-mascot-avatar" :src="mascotAvatar" />
<view class="ai-entry-text">
<text class="ai-entry-title">让 {{ mascotName }} 帮你解读</text>
<text class="ai-entry-desc">基于这份报告,{{ mascotName }} 会给出个建议</text>
</view>
<text class="ai-entry-arrow">›</text>
</view>
goAIChat 方法:
goAIChat() {
const userInfo = uni.getStorageSync('userInfo')
const mascot = userInfo && userInfo.mascot === 'fubao' ? '福宝' : '浠宝'
uni.navigateTo({
url: '/pages/ai/chat?mode=report&reportId=' + this.reportId + '&firstMessage='
+ encodeURIComponent('你好' + mascot + ',帮我看看这份报告有什么需要注意的地方?')
})
}
验收: 每个报告详情页底部可见 AI 入口,点击后跳转 chat.vue 并自动发送解读请求
目标: 在 AI 上下文中注入用户当前任务状态,让 Dify 感知任务进度
文件: cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java
改动点: buildContext() 末尾增加:
// 6. 当前未完成任务列表
List<Map<String, Object>> pendingTasks = getPendingTasks(userId);
if (!pendingTasks.isEmpty()) {
ctx.put("pendingTasks", pendingTasks);
}
private List<Map<String, Object>> getPendingTasks(Long userId) {
// 查询当前用户未完成的 growth_task_log 关联的 growth_tasks
// 返回 title, type, dimension 等信息
}
验收: 用户有未完成任务时,Dify inputs 中包含 pendingTasks 字段
目标: 确保 Phase 1 所有后端改动编译通过,无路由冲突
cd cfc-backend && mvn clean compile
grep -rn '@Mapping' src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u | grep -E '(task|ai)'
验收: mvn clean compile exit 0,新增路由无重复
平台配置(在 Dify 平台操作,非代码):
/api/ai/context(保留原有记忆逻辑)后端适配:
AIService.java:统一使用新 API Key,删除 nutritionApiKey/tongueApiKey验收: 现有所有 AI 功能在新 App 上正常运作
文件: cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java
改动点:
search() 方法中增加排序逻辑:
ProductOrder 中已购买过的商品排后)createdAt 时效性排序后端: 新建 POST /api/tasks/accept-dynamic
@PostMapping("/api/tasks/accept-dynamic")
public Result<Long> acceptDynamicTask(@RequestAttribute("userId") Long userId,
@RequestBody Map<String, Object> params) {
// 参数:title, description, deadline, dimension, conversationId
// 调用 growthTaskService.createDynamicTask()
// 返回 taskId
}
前端: AITaskCard 组件点击"接受任务" → 调用此接口 → 显示成功提示
| 开发 | 任务 |
|---|---|
| 后端 A | 1.1 DB迁移 + 1.2 TaskParseService + 1.3 createDynamicTask |
| 后端 B | 1.4 Controller改造 + 1.8 FamilyContext增强 + 1.9 编译验证 |
| 前端 A | 1.5 AITaskCard组件 + 1.6 chat.vue改造 |
| 前端 B | 1.7 各报告详情页AI入口 |
| 并行启动 | 1.1/1.2 可先启动(无依赖),1.5 可先启动(无依赖) |
ALTER TABLE ... MODIFY COLUMN 改回原 ENUM,DROP COLUMN 新字段git revert 对应提交