Эх сурвалжийг харах

feat: 任务添加五维维度参数支持

E2E Test Bot 1 сар өмнө
parent
commit
13ab44e62d

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -7017,5 +7017,14 @@ private void runMigration100() {
 		} catch (Exception e) {
 		} catch (Exception e) {
 			// 表已存在,忽略
 			// 表已存在,忽略
 		}
 		}
+
+		// 迁移107: tasks表添加dimension_code和dimension_weights列(五维维度支持)
+		try {
+			ensureColumn("tasks", "dimension_code", "VARCHAR(20) COMMENT '五维维度: body/mind/wisdom/action/wealth'");
+			ensureColumn("tasks", "dimension_weights", "JSON COMMENT '五维权重: {\"body\":0,\"mind\":0,\"wisdom\":0,\"action\":0,\"wealth\":0}'");
+			log.info("已完成tasks表维度字段添加");
+		} catch (Exception e) {
+			log.warn("添加tasks表维度字段失败: {}", e.getMessage());
+		}
 	}
 	}
 }
 }

+ 2 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/task/TaskController.java

@@ -48,11 +48,12 @@ public class TaskController {
     public Result<List<Task>> getTodayTasks(@RequestBody Map<String, Object> params) {
     public Result<List<Task>> getTodayTasks(@RequestBody Map<String, Object> params) {
         Long childId = Long.valueOf(params.get("childId").toString());
         Long childId = Long.valueOf(params.get("childId").toString());
         String category = params.get("category") != null ? params.get("category").toString() : null;
         String category = params.get("category") != null ? params.get("category").toString() : null;
+        String dimensionCode = params.get("dimensionCode") != null ? params.get("dimensionCode").toString() : null;
         List<Task> tasks;
         List<Task> tasks;
         if (category != null && !category.isEmpty()) {
         if (category != null && !category.isEmpty()) {
             tasks = taskService.getTodayTasksByCategory(childId, category);
             tasks = taskService.getTodayTasksByCategory(childId, category);
         } else {
         } else {
-            tasks = taskService.getTodayTasks(childId);
+            tasks = taskService.getTodayTasks(childId, dimensionCode);
         }
         }
         return Result.success(tasks);
         return Result.success(tasks);
     }
     }

+ 5 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/CreateTaskDTO.java

@@ -45,4 +45,9 @@ public class CreateTaskDTO {
     private String repeatType;
     private String repeatType;
     // 截止时间(兼容旧版)
     // 截止时间(兼容旧版)
     private Date deadline;
     private Date deadline;
+
+    // 五维维度: body/mind/wisdom/action/wealth
+    private String dimensionCode;
+    // 五维权重JSON: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}
+    private String dimensionWeights;
 }
 }

+ 12 - 6
cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java

@@ -92,13 +92,19 @@ private Date latestEnd;
     // 小游戏代码(当category为'小游戏类'时使用): schulte/1a2b/sudoku
     // 小游戏代码(当category为'小游戏类'时使用): schulte/1a2b/sudoku
     private String minigameCode;
     private String minigameCode;
 
 
-    // 来源类型: article(文章阅读) / activity(活动签到) / manual(手动创建)
-    private String sourceType;
+// 来源类型: article(文章阅读) / activity(活动签到) / manual(手动创建)
+	    private String sourceType;
 
 
-    // 来源ID(如文章ID、活动ID)
-    private Long sourceId;
+	    // 来源ID(如文章ID、活动ID)
+	    private Long sourceId;
 
 
-    private Date createdAt;
+	    /** 五维维度: body/mind/wisdom/action/wealth */
+	    private String dimensionCode;
 
 
-    private Date updatedAt;
+	    /** JSON: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0} */
+	    private String dimensionWeights;
+
+	    private Date createdAt;
+
+	    private Date updatedAt;
 }
 }

+ 24 - 11
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java

@@ -188,15 +188,19 @@ public class TaskService implements TaskServiceInterface {
             task.setNeedReview(dto.getNeedReview() != null ? dto.getNeedReview() : 1);
             task.setNeedReview(dto.getNeedReview() != null ? dto.getNeedReview() : 1);
         }
         }
 
 
-        task.setReviewByCategory(dto.getReviewByCategory() != null ? dto.getReviewByCategory() : 0);
+task.setReviewByCategory(dto.getReviewByCategory() != null ? dto.getReviewByCategory() : 0);
         task.setStatus("pending");
         task.setStatus("pending");
         task.setCreatedAt(new Date());
         task.setCreatedAt(new Date());
         task.setUpdatedAt(new Date());
         task.setUpdatedAt(new Date());
-        
+
+        // 五维维度
+        task.setDimensionCode(dto.getDimensionCode());
+        task.setDimensionWeights(dto.getDimensionWeights());
+
         return task;
         return task;
-    }
+    }
 
 
-    public List<Task> getTodayTasks(Long childId) {
+public List<Task> getTodayTasks(Long childId, String dimensionCode) {
         Calendar calendar = Calendar.getInstance();
         Calendar calendar = Calendar.getInstance();
         calendar.set(Calendar.HOUR_OF_DAY, 0);
         calendar.set(Calendar.HOUR_OF_DAY, 0);
         calendar.set(Calendar.MINUTE, 0);
         calendar.set(Calendar.MINUTE, 0);
@@ -207,13 +211,22 @@ public class TaskService implements TaskServiceInterface {
         calendar.add(Calendar.DAY_OF_MONTH, 1);
         calendar.add(Calendar.DAY_OF_MONTH, 1);
         Date endOfDay = calendar.getTime();
         Date endOfDay = calendar.getTime();
 
 
-return taskMapper.selectList(new LambdaQueryWrapper<Task>()
-.eq(Task::getChildId, childId)
-.ge(Task::getDeadline, startOfDay)
-.lt(Task::getDeadline, endOfDay)
-.in(Task::getStatus, Arrays.asList("pending", "completed"))
-.orderByAsc(Task::getDeadline));
-}
+        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .ge(Task::getDeadline, startOfDay)
+            .lt(Task::getDeadline, endOfDay)
+            .in(Task::getStatus, Arrays.asList("pending", "completed"))
+            .orderByAsc(Task::getDeadline);
+
+        if (dimensionCode != null && !dimensionCode.isEmpty()) {
+            wrapper.and(w -> w
+                .apply("COALESCE(JSON_EXTRACT(dimension_weights, '$." + dimensionCode + "'), 0) > 0")
+                .or().eq(Task::getDimensionCode, dimensionCode)
+            );
+        }
+
+        return taskMapper.selectList(wrapper);
+    }
 
 
     /**
     /**
      * 按维度分类获取今日任务(五维页面筛选用)
      * 按维度分类获取今日任务(五维页面筛选用)

+ 4 - 1
cfc-backend/src/main/resources/schema.sql

@@ -82,12 +82,15 @@ CREATE TABLE IF NOT EXISTS tasks (
     completed_at DATETIME,
     completed_at DATETIME,
     bonus_rule JSON COMMENT '积分奖励规则',
     bonus_rule JSON COMMENT '积分奖励规则',
     points_expire_days INT DEFAULT 0 COMMENT '积分有效期天数',
     points_expire_days INT DEFAULT 0 COMMENT '积分有效期天数',
+    dimension_code VARCHAR(20) COMMENT '五维维度: body/mind/wisdom/action/wealth',
+    dimension_weights JSON COMMENT '五维权重: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_child_id_deadline (child_id, deadline),
     INDEX idx_child_id_deadline (child_id, deadline),
     INDEX idx_status (status),
     INDEX idx_status (status),
     INDEX idx_category (category),
     INDEX idx_category (category),
-    INDEX idx_executor (executor_type, executor_id)
+    INDEX idx_executor (executor_type, executor_id),
+    INDEX idx_dimension_code (dimension_code)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
 -- 积分流水表
 -- 积分流水表

+ 38 - 5
docs/analysis/dimension-filter-design.md

@@ -89,13 +89,46 @@ private String buildEqualWeights(String relatedDimensions) {
 
 
 **状态:** ✅ 已实现权重感知筛选,兼容旧版 `relatedDimensions`
 **状态:** ✅ 已实现权重感知筛选,兼容旧版 `relatedDimensions`
 
 
-### 4. Task(任务)❌ 不支持维度筛选
+### 4. Task(任务)✅ 已支持维度筛选
 
 
-**实体字段:** 无维度相关字段
+**实体字段(新增):**
+```java
+private String dimensionCode;        // 新: 单值, 如 "body"
+private String dimensionWeights;     // 新: JSON, 如 {"body":30,"mind":0,"wisdom":0,"action":70,"wealth":0}
+```
+
+**创建任务时传入(CreateTaskDTO):**
+```java
+private String dimensionCode;        // 五维维度
+private String dimensionWeights;     // 五维权重JSON
+```
 
 
-Task 实体没有 `dimensionCode`、`domain`、`dimensionWeights` 等任何维度字段。任务通过 `category` 字段分类(如"学习类""行动类"),但分类与五维维度没有直接映射关系。
+**筛选逻辑(TaskService.getTodayTasks()):**
+```java
+if (dimensionCode != null && !dimensionCode.isEmpty()) {
+    wrapper.and(w -> w
+        .apply("COALESCE(JSON_EXTRACT(dimension_weights, '$." + dimensionCode + "'), 0) > 0")
+        .or().eq(Task::getDimensionCode, dimensionCode)
+    );
+}
+```
+
+**调用方式:**
+```json
+POST /api/tasks/today
+{"childId": 1003, "dimensionCode": "body"}
+```
+
+**状态:** ✅ 已实现权重感知筛选,与活动/商品/文章统一模式
+
+### 数据库迁移
+
+```sql
+ALTER TABLE tasks ADD COLUMN dimension_code VARCHAR(20) COMMENT '五维维度: body/mind/wisdom/action/wealth';
+ALTER TABLE tasks ADD COLUMN dimension_weights JSON COMMENT '五维权重';
+```
 
 
-**状态:** ❌ 不支持维度筛选
+迁移编号:`迁移107`,在 DatabaseInitializer.runMigrations() 中。| 任务 | `POST /api/tasks/today` | `dimensionCode` | `{"dimensionCode":"body"}` |
 
 
 ## 架构设计
 ## 架构设计
 
 
@@ -141,7 +174,7 @@ COALESCE(JSON_EXTRACT(dimension_weights, '$.body'), 0) > 0
 | 活动 | `POST /api/activity/list` | `dimensionCode` | `{"dimensionCode":"body"}` |
 | 活动 | `POST /api/activity/list` | `dimensionCode` | `{"dimensionCode":"body"}` |
 | 商品 | `POST /api/product/list` | `domain` | `{"domain":"body"}` |
 | 商品 | `POST /api/product/list` | `domain` | `{"domain":"body"}` |
 | 文章 | `POST /api/articles/list` | `dimensionCode` | `{"dimensionCode":"body"}` |
 | 文章 | `POST /api/articles/list` | `dimensionCode` | `{"dimensionCode":"body"}` |
-| 任务 | `POST /api/tasks/today` | 不支持 | — |
+| 任务 | `POST /api/tasks/today` | `dimensionCode` | `{"childId":1003,"dimensionCode":"body"}` |
 
 
 ## 建议
 ## 建议