Просмотр исходного кода

feat(profile): 打卡和任务完成后自动触发画像重算

Sisyphus 4 недель назад
Родитель
Сommit
cbbc5e8a23

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthExerciseController.java

@@ -23,6 +23,9 @@ public class HealthExerciseController {
     @Resource
     private HealthExerciseRecordMapper healthExerciseRecordMapper;
 
+    @Resource
+    private com.etotem.cfc.service.ProfileComputeService profileComputeService;
+
     @Operation(summary = "获取运动打卡列表")
     @PostMapping("/list")
     public Result<List<HealthExerciseRecord>> list(@RequestBody Map<String, Object> params,
@@ -44,6 +47,15 @@ public class HealthExerciseController {
         }
         record.setCreatedAt(new Date());
         healthExerciseRecordMapper.insert(record);
+                // 触发画像重算
+        try {
+            Long triggerMemberId = record.getMemberId();
+            if (triggerMemberId != null) {
+                new Thread(() -> profileComputeService.computeAndSave(triggerMemberId)).start();
+            }
+        } catch (Exception e) {
+            // 画像计算失败不影响打卡主流程
+        }
         return Result.success(record);
     }
 }

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthMealController.java

@@ -27,6 +27,9 @@ public class HealthMealController {
     @Resource
     private PointsService pointsService;
 
+    @Resource
+    private com.etotem.cfc.service.ProfileComputeService profileComputeService;
+
     @Operation(summary = "获取饮食打卡列表")
     @PostMapping("/list")
     public Result<List<HealthMealRecord>> list(
@@ -50,6 +53,15 @@ public class HealthMealController {
             pointsService.awardCheckinPoints(record.getMemberId(), 5, "饮食打卡");
         } catch (Exception e) {
             // 积分发放失败不影响打卡记录
+        }
+                // 触发画像重算
+        try {
+            Long triggerMemberId = record.getMemberId();
+            if (triggerMemberId != null) {
+                new Thread(() -> profileComputeService.computeAndSave(triggerMemberId)).start();
+            }
+        } catch (Exception e) {
+            // 画像计算失败不影响打卡主流程
         }
         return Result.success(record);
     }

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthSleepController.java

@@ -23,6 +23,9 @@ public class HealthSleepController {
     @Resource
     private HealthSleepRecordMapper healthSleepRecordMapper;
 
+    @Resource
+    private com.etotem.cfc.service.ProfileComputeService profileComputeService;
+
     @Operation(summary = "获取睡眠打卡列表")
     @PostMapping("/list")
     public Result<List<HealthSleepRecord>> list(@RequestBody Map<String, Object> params,
@@ -44,6 +47,15 @@ public class HealthSleepController {
         }
         record.setCreatedAt(new Date());
         healthSleepRecordMapper.insert(record);
+                // 触发画像重算
+        try {
+            Long triggerMemberId = record.getMemberId();
+            if (triggerMemberId != null) {
+                new Thread(() -> profileComputeService.computeAndSave(triggerMemberId)).start();
+            }
+        } catch (Exception e) {
+            // 画像计算失败不影响打卡主流程
+        }
         return Result.success(record);
     }
 }

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/mind/EmotionCheckinController.java

@@ -24,12 +24,24 @@ public class EmotionCheckinController {
 
     @Resource
     private EmotionCheckinService emotionCheckinService;
+    @Resource
+    private com.etotem.cfc.service.ProfileComputeService profileComputeService;
+
 
     @PostMapping("/create")
     public Result<?> create(@RequestBody EmotionCheckinDTO dto,
                             @RequestAttribute("userId") Long userId) {
         try {
             Object result = emotionCheckinService.createCheckin(dto, userId);
+                        // 触发画像重算
+            try {
+                Long triggerMemberId = dto.getMemberId();
+                if (triggerMemberId != null) {
+                    new Thread(() -> profileComputeService.computeAndSave(triggerMemberId)).start();
+                }
+            } catch (Exception e) {
+                // 画像计算失败不影响打卡主流程
+            }
             return Result.success(result);
         } catch (Exception e) {
             log.error("创建情绪打卡失败", e);

+ 315 - 303
cfc-backend/src/main/java/com/etotem/cfc/controller/task/TaskController.java

@@ -1,307 +1,319 @@
-package com.etotem.cfc.controller.task;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.etotem.cfc.common.Result;
-import com.etotem.cfc.dto.CompleteTaskDTO;
-import com.etotem.cfc.dto.CreateTaskDTO;
-import com.etotem.cfc.dto.TaskReviewDTO;
-import com.etotem.cfc.entity.MiniGame;
-import com.etotem.cfc.entity.Task;
-import com.etotem.cfc.mapper.TaskMapper;
-import com.etotem.cfc.service.MembershipService;
-import com.etotem.cfc.service.MiniGameService;
-import com.etotem.cfc.service.ProblemCompletionService;
-import com.etotem.cfc.service.TaskService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import javax.annotation.Resource;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-import java.util.Map;
+package com.etotem.cfc.controller.task;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.CompleteTaskDTO;
+import com.etotem.cfc.dto.CreateTaskDTO;
+import com.etotem.cfc.dto.TaskReviewDTO;
+import com.etotem.cfc.entity.MiniGame;
+import com.etotem.cfc.entity.Task;
+import com.etotem.cfc.mapper.TaskMapper;
+import com.etotem.cfc.service.MembershipService;
+import com.etotem.cfc.service.MiniGameService;
+import com.etotem.cfc.service.ProblemCompletionService;
+import com.etotem.cfc.service.TaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
 import com.etotem.cfc.util.ParamUtils;
-
-@Tag(name = "任务管理", description = "任务的创建、完成、审核、历史查询等接口")
-@RestController
-@RequestMapping("/api/tasks")
-public class TaskController {
-
-    @Resource
-    private TaskService taskService;
-
-    @Resource
-    private MiniGameService miniGameService;
-
-    @Resource
-    private TaskMapper taskMapper;
-
-    @Resource
-    private MembershipService membershipService;
-
-    @Resource
-    private ProblemCompletionService problemCompletionService;
-
-    /**
-     * 会员任务校验:task.memberOnly==1 且非会员 → 拒绝(提示开通会员)
-     */
-    private Result<?> checkMemberOnly(Long taskId) {
-        Task task = taskMapper.selectById(taskId);
-        if (task == null) {
-            return Result.error("任务不存在");
-        }
-        if (task.getMemberOnly() != null && task.getMemberOnly() == 1) {
-            boolean isMember = membershipService.getCurrentLevel(task.getFamilyId()) != null
-                    && !"FREE".equals(membershipService.getCurrentLevel(task.getFamilyId()).getLevelCode());
-            if (!isMember) {
-                return Result.error("该任务是会员任务,请先开通会员后再操作");
-            }
-        }
-        return null;
-    }
-
-    @Operation(summary = "获取可选择的小游戏列表")
-    @PostMapping("/minigame-options")
-    public Result<List<MiniGame>> getMinigameOptions() {
-        List<MiniGame> games = miniGameService.getEnabledGames();
-        return Result.success(games);
-    }
-
-    @Operation(summary = "创建任务")
-    @PostMapping("/create")
-    public Result<Long> createTask(@RequestAttribute("userId") Long userId,
-                                   @RequestBody CreateTaskDTO dto) {
-        Long taskId = taskService.createTask(userId, dto);
-        return Result.success(taskId);
-    }
-
-    @Operation(summary = "获取今日任务")
-    @PostMapping("/today")
-    public Result<List<Task>> getTodayTasks(@RequestBody Map<String, Object> params,
-                                            @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
-        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
-        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;
-        if (category != null && !category.isEmpty()) {
-            tasks = taskService.getTodayTasksByCategory(memberId, category);
-        } else {
-            tasks = taskService.getTodayTasks(memberId, dimensionCode);
-        }
-        return Result.success(tasks);
-    }
-
-    @Operation(summary = "完成任务")
-    @PostMapping("/{id}/complete")
-    public Result<Map<String, Object>> completeTask(@PathVariable Long id,
-                                                    @RequestBody CompleteTaskDTO dto,
-                                                    @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
-        Result<?> memberCheck = checkMemberOnly(id);
-        if (memberCheck != null) {
-            return Result.error(memberCheck.getMessage());
-        }
-        Long memberId = dto.getMemberId() != null ? dto.getMemberId() : currentMemberId;
-        Map<String, Object> result = taskService.completeTask(id, memberId, dto.getPhotoUrl(), dto.getContent(), dto.getContentType());
-        if (result != null) {
-            // B17: 任务完成后检查问题域奖励
-            try {
-                problemCompletionService.checkAndRewardByTask(id);
-            } catch (Exception e) {
-                // 奖励发放失败不影响任务完成
-            }
-        }
-        return Result.success(result);
-    }
-
-    @Operation(summary = "开始任务(两阶段任务入口)")
-    @PostMapping("/{id}/start")
-    public Result<Map<String, Object>> startTask(@PathVariable Long id,
-                                                 @RequestBody(required = false) Map<String, Object> params,
-                                                 @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
-        Result<?> memberCheck = checkMemberOnly(id);
-        if (memberCheck != null) {
-            return Result.error(memberCheck.getMessage());
-        }
-        Long memberId = params != null && params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
-        if (memberId == null) {
-            return Result.error("memberId不能为空");
-        }
-        try {
-            Map<String, Object> result = taskService.startTask(id, memberId);
-            return Result.success(result);
-        } catch (Exception e) {
-            return Result.error(e.getMessage());
-        }
+
+@Tag(name = "任务管理", description = "任务的创建、完成、审核、历史查询等接口")
+@RestController
+@RequestMapping("/api/tasks")
+public class TaskController {
+
+    @Resource
+    private TaskService taskService;
+
+    @Resource
+    private MiniGameService miniGameService;
+
+    @Resource
+    private TaskMapper taskMapper;
+
+    @Resource
+    private MembershipService membershipService;
+
+    @Resource
+    private ProblemCompletionService problemCompletionService;
+    @Resource
+    private com.etotem.cfc.service.ProfileComputeService profileComputeService;
+
+
+    /**
+     * 会员任务校验:task.memberOnly==1 且非会员 → 拒绝(提示开通会员)
+     */
+    private Result<?> checkMemberOnly(Long taskId) {
+        Task task = taskMapper.selectById(taskId);
+        if (task == null) {
+            return Result.error("任务不存在");
+        }
+        if (task.getMemberOnly() != null && task.getMemberOnly() == 1) {
+            boolean isMember = membershipService.getCurrentLevel(task.getFamilyId()) != null
+                    && !"FREE".equals(membershipService.getCurrentLevel(task.getFamilyId()).getLevelCode());
+            if (!isMember) {
+                return Result.error("该任务是会员任务,请先开通会员后再操作");
+            }
+        }
+        return null;
     }
-
-    @Operation(summary = "审核任务")
-    @PostMapping("/{id}/review")
-    public Result<Boolean> reviewTask(@PathVariable Long id,
-                                      @RequestBody TaskReviewDTO dto) {
-        boolean success = taskService.reviewTask(id, dto);
-        return Result.success(success);
-    }
-
-  @Operation(summary = "获取任务历史")
-  @PostMapping("/history")
-  public Result<Page<Task>> getTaskHistory(@RequestBody Map<String, Object> params,
-                                           @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
-    Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
-    Integer page = params.get("page") != null ? Integer.valueOf(params.get("page").toString()) : 1;
-    Integer size = params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 10;
-    String category = params.get("category") != null ? params.get("category").toString() : null;
-    Page<Task> history;
-    if (category != null && !category.isEmpty()) {
-        history = taskService.getTaskHistoryByCategory(memberId, page, size, category);
-    } else {
-        history = taskService.getTaskHistory(memberId, page, size);
-    }
-    return Result.success(history);
+
+    @Operation(summary = "获取可选择的小游戏列表")
+    @PostMapping("/minigame-options")
+    public Result<List<MiniGame>> getMinigameOptions() {
+        List<MiniGame> games = miniGameService.getEnabledGames();
+        return Result.success(games);
+    }
+
+    @Operation(summary = "创建任务")
+    @PostMapping("/create")
+    public Result<Long> createTask(@RequestAttribute("userId") Long userId,
+                                   @RequestBody CreateTaskDTO dto) {
+        Long taskId = taskService.createTask(userId, dto);
+        return Result.success(taskId);
+    }
+
+    @Operation(summary = "获取今日任务")
+    @PostMapping("/today")
+    public Result<List<Task>> getTodayTasks(@RequestBody Map<String, Object> params,
+                                            @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
+        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
+        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;
+        if (category != null && !category.isEmpty()) {
+            tasks = taskService.getTodayTasksByCategory(memberId, category);
+        } else {
+            tasks = taskService.getTodayTasks(memberId, dimensionCode);
+        }
+        return Result.success(tasks);
+    }
+
+    @Operation(summary = "完成任务")
+    @PostMapping("/{id}/complete")
+    public Result<Map<String, Object>> completeTask(@PathVariable Long id,
+                                                    @RequestBody CompleteTaskDTO dto,
+                                                    @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
+        Result<?> memberCheck = checkMemberOnly(id);
+        if (memberCheck != null) {
+            return Result.error(memberCheck.getMessage());
+        }
+        Long memberId = dto.getMemberId() != null ? dto.getMemberId() : currentMemberId;
+        Map<String, Object> result = taskService.completeTask(id, memberId, dto.getPhotoUrl(), dto.getContent(), dto.getContentType());
+        if (result != null) {
+            // B17: 任务完成后检查问题域奖励
+            try {
+                problemCompletionService.checkAndRewardByTask(id);
+            } catch (Exception e) {
+                // 奖励发放失败不影响任务完成
+            }
+            // 触发画像重算
+            try {
+                Long triggerMemberId = memberId;
+                if (triggerMemberId != null) {
+                    new Thread(() -> profileComputeService.computeAndSave(triggerMemberId)).start();
+                }
+            } catch (Exception e) {
+                // 画像计算失败不影响任务完成
+            }
+        }
+        return Result.success(result);
+    }
+
+    @Operation(summary = "开始任务(两阶段任务入口)")
+    @PostMapping("/{id}/start")
+    public Result<Map<String, Object>> startTask(@PathVariable Long id,
+                                                 @RequestBody(required = false) Map<String, Object> params,
+                                                 @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
+        Result<?> memberCheck = checkMemberOnly(id);
+        if (memberCheck != null) {
+            return Result.error(memberCheck.getMessage());
+        }
+        Long memberId = params != null && params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
+        if (memberId == null) {
+            return Result.error("memberId不能为空");
+        }
+        try {
+            Map<String, Object> result = taskService.startTask(id, memberId);
+            return Result.success(result);
+        } catch (Exception e) {
+            return Result.error(e.getMessage());
+        }
+    }
+
+    @Operation(summary = "审核任务")
+    @PostMapping("/{id}/review")
+    public Result<Boolean> reviewTask(@PathVariable Long id,
+                                      @RequestBody TaskReviewDTO dto) {
+        boolean success = taskService.reviewTask(id, dto);
+        return Result.success(success);
+    }
+
+  @Operation(summary = "获取任务历史")
+  @PostMapping("/history")
+  public Result<Page<Task>> getTaskHistory(@RequestBody Map<String, Object> params,
+                                           @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
+    Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
+    Integer page = params.get("page") != null ? Integer.valueOf(params.get("page").toString()) : 1;
+    Integer size = params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 10;
+    String category = params.get("category") != null ? params.get("category").toString() : null;
+    Page<Task> history;
+    if (category != null && !category.isEmpty()) {
+        history = taskService.getTaskHistoryByCategory(memberId, page, size, category);
+    } else {
+        history = taskService.getTaskHistory(memberId, page, size);
+    }
+    return Result.success(history);
   }
-
-    @Operation(summary = "获取待审核任务")
-    @PostMapping("/pending-review")
-    public Result<List<Task>> getPendingReviewTasks(@RequestAttribute("userId") Long userId) {
-    List<Task> tasks = taskService.getPendingReviewTasks(userId);
-    return Result.success(tasks);
-  }
-
-    @Operation(summary = "获取家长今日任务")
-    @PostMapping("/today-parent")
-    public Result<List<Task>> getTodayParentTasks(@RequestAttribute("userId") Long userId) {
-        List<Task> tasks = taskService.getTodayParentTasks(userId);
-        return Result.success(tasks);
-    }
-
-    @Operation(summary = "家长完成任务")
-    @PostMapping("/{id}/complete-parent")
-    public Result<Map<String, Object>> completeParentTask(@PathVariable Long id,
-                                                        @RequestAttribute("userId") Long userId) {
-        Result<?> memberCheck = checkMemberOnly(id);
-        if (memberCheck != null) {
-            return Result.error(memberCheck.getMessage());
-        }
-        Map<String, Object> result = taskService.completeParentTask(id, userId);
-        if (result != null) {
-            // B17: 任务完成后检查问题域奖励
-            try {
-                problemCompletionService.checkAndRewardByTask(id);
-            } catch (Exception e) {
-                // 奖励发放失败不影响任务完成
-            }
-        }
-        return Result.success(result);
-    }
-
-    @Operation(summary = "删除任务")
-    @PostMapping("/{id}")
-    public Result<Boolean> deleteTask(@PathVariable Long id,
-            @RequestAttribute("userId") Long userId) {
-        boolean success = taskService.deleteTask(id, userId);
-        return Result.success(success);
-    }
-
-    @Operation(summary = "完成小游戏任务")
-    @PostMapping("/{id}/complete-minigame")
-    public Result<Map<String, Object>> completeMinigameTask(
-            @PathVariable Long id,
-            @RequestBody Map<String, Object> body,
-            @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
-        Result<?> memberCheck = checkMemberOnly(id);
-        if (memberCheck != null) {
-            return Result.error(memberCheck.getMessage());
-        }
-        Long memberId = body.get("memberId") != null ? ParamUtils.getLong(body.get("memberId")) : currentMemberId;
-        Integer completionTime = body.get("completionTime") != null ?
-            Integer.valueOf(body.get("completionTime").toString()) : null;
-        Integer score = body.get("score") != null ?
-            Integer.valueOf(body.get("score").toString()) : null;
-
-        try {
-            Map<String, Object> result = taskService.completeMinigameTask(id, memberId, completionTime, score);
-            return Result.success(result);
-        } catch (Exception e) {
-            return Result.error(e.getMessage());
-        }
-    }
-
-    @Operation(summary = "批量完成任务")
-    @PostMapping("/batch-complete")
-    public Result<Boolean> batchComplete(@RequestBody Map<String, Object> params,
-                                         @RequestAttribute("userId") Long userId) {
-        List<Long> taskIds = (List<Long>) params.get("taskIds");
-        if (taskIds == null || taskIds.isEmpty()) {
-            return Result.error("taskIds不能为空");
-        }
-        // 会员任务校验:任一任务为会员任务且非会员 → 拒绝整批
-        for (Long taskId : taskIds) {
-            Result<?> memberCheck = checkMemberOnly(taskId);
-            if (memberCheck != null) {
-                return Result.error(memberCheck.getMessage());
-            }
-        }
-        try {
-            taskService.batchComplete(taskIds, userId);
-            return Result.success(true);
-        } catch (Exception e) {
-            return Result.error("批量操作失败: " + e.getMessage());
-        }
-    }
-
-    @Operation(summary = "批量删除任务")
-    @PostMapping("/batch-delete")
-    public Result<Boolean> batchDelete(@RequestBody Map<String, Object> params,
-                                       @RequestAttribute("userId") Long userId) {
-        List<Long> taskIds = (List<Long>) params.get("taskIds");
-        if (taskIds == null || taskIds.isEmpty()) {
-            return Result.error("taskIds不能为空");
-        }
-        try {
-            taskService.batchDelete(taskIds, userId);
-            return Result.success(true);
-        } catch (Exception e) {
-            return Result.error("批量操作失败: " + e.getMessage());
-        }
-    }
-
-    @Operation(summary = "获取我收到的待接受任务列表")
-    @PostMapping("/my-pending")
-    public Result<List<Task>> getMyPendingTasks(@RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
-                                                @RequestBody Map<String, Object> params) {
-        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
-        if (memberId == null) {
-            return Result.error("memberId不能为空");
-        }
-        List<Task> tasks = taskService.getMyPendingTasks(memberId);
-        return Result.success(tasks);
-    }
-
-    @Operation(summary = "接受任务")
-    @PostMapping("/accept")
-    public Result<Boolean> acceptTask(@PathVariable Long id,
-                                      @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
-                                      @RequestBody Map<String, Object> params) {
-        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
-        if (memberId == null) {
-            return Result.error("memberId不能为空");
-        }
-        Result<?> memberCheck = checkMemberOnly(id);
-        if (memberCheck != null) {
-            return Result.error(memberCheck.getMessage());
-        }
-        boolean success = taskService.acceptTask(id, memberId);
-        return Result.success(success);
-    }
-
-    @Operation(summary = "拒绝任务")
-    @PostMapping("/reject/{id}")
-    public Result<Boolean> rejectTask(@PathVariable Long id,
-                                      @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
-                                      @RequestBody Map<String, Object> params) {
-        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
-        if (memberId == null) {
-            return Result.error("memberId不能为空");
-        }
-        boolean success = taskService.rejectTask(id, memberId);
-        return Result.success(success);
-    }
+
+    @Operation(summary = "获取待审核任务")
+    @PostMapping("/pending-review")
+    public Result<List<Task>> getPendingReviewTasks(@RequestAttribute("userId") Long userId) {
+    List<Task> tasks = taskService.getPendingReviewTasks(userId);
+    return Result.success(tasks);
+  }
+
+    @Operation(summary = "获取家长今日任务")
+    @PostMapping("/today-parent")
+    public Result<List<Task>> getTodayParentTasks(@RequestAttribute("userId") Long userId) {
+        List<Task> tasks = taskService.getTodayParentTasks(userId);
+        return Result.success(tasks);
+    }
+
+    @Operation(summary = "家长完成任务")
+    @PostMapping("/{id}/complete-parent")
+    public Result<Map<String, Object>> completeParentTask(@PathVariable Long id,
+                                                        @RequestAttribute("userId") Long userId) {
+        Result<?> memberCheck = checkMemberOnly(id);
+        if (memberCheck != null) {
+            return Result.error(memberCheck.getMessage());
+        }
+        Map<String, Object> result = taskService.completeParentTask(id, userId);
+        if (result != null) {
+            // B17: 任务完成后检查问题域奖励
+            try {
+                problemCompletionService.checkAndRewardByTask(id);
+            } catch (Exception e) {
+                // 奖励发放失败不影响任务完成
+            }
+        }
+        return Result.success(result);
+    }
+
+    @Operation(summary = "删除任务")
+    @PostMapping("/{id}")
+    public Result<Boolean> deleteTask(@PathVariable Long id,
+            @RequestAttribute("userId") Long userId) {
+        boolean success = taskService.deleteTask(id, userId);
+        return Result.success(success);
+    }
+
+    @Operation(summary = "完成小游戏任务")
+    @PostMapping("/{id}/complete-minigame")
+    public Result<Map<String, Object>> completeMinigameTask(
+            @PathVariable Long id,
+            @RequestBody Map<String, Object> body,
+            @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
+        Result<?> memberCheck = checkMemberOnly(id);
+        if (memberCheck != null) {
+            return Result.error(memberCheck.getMessage());
+        }
+        Long memberId = body.get("memberId") != null ? ParamUtils.getLong(body.get("memberId")) : currentMemberId;
+        Integer completionTime = body.get("completionTime") != null ?
+            Integer.valueOf(body.get("completionTime").toString()) : null;
+        Integer score = body.get("score") != null ?
+            Integer.valueOf(body.get("score").toString()) : null;
+
+        try {
+            Map<String, Object> result = taskService.completeMinigameTask(id, memberId, completionTime, score);
+            return Result.success(result);
+        } catch (Exception e) {
+            return Result.error(e.getMessage());
+        }
+    }
+
+    @Operation(summary = "批量完成任务")
+    @PostMapping("/batch-complete")
+    public Result<Boolean> batchComplete(@RequestBody Map<String, Object> params,
+                                         @RequestAttribute("userId") Long userId) {
+        List<Long> taskIds = (List<Long>) params.get("taskIds");
+        if (taskIds == null || taskIds.isEmpty()) {
+            return Result.error("taskIds不能为空");
+        }
+        // 会员任务校验:任一任务为会员任务且非会员 → 拒绝整批
+        for (Long taskId : taskIds) {
+            Result<?> memberCheck = checkMemberOnly(taskId);
+            if (memberCheck != null) {
+                return Result.error(memberCheck.getMessage());
+            }
+        }
+        try {
+            taskService.batchComplete(taskIds, userId);
+            return Result.success(true);
+        } catch (Exception e) {
+            return Result.error("批量操作失败: " + e.getMessage());
+        }
+    }
+
+    @Operation(summary = "批量删除任务")
+    @PostMapping("/batch-delete")
+    public Result<Boolean> batchDelete(@RequestBody Map<String, Object> params,
+                                       @RequestAttribute("userId") Long userId) {
+        List<Long> taskIds = (List<Long>) params.get("taskIds");
+        if (taskIds == null || taskIds.isEmpty()) {
+            return Result.error("taskIds不能为空");
+        }
+        try {
+            taskService.batchDelete(taskIds, userId);
+            return Result.success(true);
+        } catch (Exception e) {
+            return Result.error("批量操作失败: " + e.getMessage());
+        }
+    }
+
+    @Operation(summary = "获取我收到的待接受任务列表")
+    @PostMapping("/my-pending")
+    public Result<List<Task>> getMyPendingTasks(@RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
+                                                @RequestBody Map<String, Object> params) {
+        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
+        if (memberId == null) {
+            return Result.error("memberId不能为空");
+        }
+        List<Task> tasks = taskService.getMyPendingTasks(memberId);
+        return Result.success(tasks);
+    }
+
+    @Operation(summary = "接受任务")
+    @PostMapping("/accept")
+    public Result<Boolean> acceptTask(@PathVariable Long id,
+                                      @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
+                                      @RequestBody Map<String, Object> params) {
+        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
+        if (memberId == null) {
+            return Result.error("memberId不能为空");
+        }
+        Result<?> memberCheck = checkMemberOnly(id);
+        if (memberCheck != null) {
+            return Result.error(memberCheck.getMessage());
+        }
+        boolean success = taskService.acceptTask(id, memberId);
+        return Result.success(success);
+    }
+
+    @Operation(summary = "拒绝任务")
+    @PostMapping("/reject/{id}")
+    public Result<Boolean> rejectTask(@PathVariable Long id,
+                                      @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
+                                      @RequestBody Map<String, Object> params) {
+        Long memberId = params.get("memberId") != null ? ParamUtils.getLong(params.get("memberId")) : currentMemberId;
+        if (memberId == null) {
+            return Result.error("memberId不能为空");
+        }
+        boolean success = taskService.rejectTask(id, memberId);
+        return Result.success(success);
+    }
 }

+ 14 - 13
cfc-backend/src/main/java/com/etotem/cfc/service/impl/ProfileComputeServiceImpl.java

@@ -11,6 +11,7 @@ import org.springframework.stereotype.Service;
 
 import java.sql.Timestamp;
 import java.time.LocalDate;
+import java.util.Date;
 import java.time.LocalDateTime;
 import java.util.*;
 
@@ -43,7 +44,7 @@ public class ProfileComputeServiceImpl implements ProfileComputeService {
         JSONObject dimensionScores = computeDimensionScores(bodyMetrics, mindMetrics, wisdomMetrics, actionMetrics, wealthMetrics);
         String problemDomains = computeProblemDomains(memberId);
 
-        LocalDateTime computedAt = LocalDateTime.now();
+        Date computedAt = new Date();
         JSONObject allMetrics = new JSONObject();
         allMetrics.put("dimension_scores", dimensionScores);
         allMetrics.put("body_metrics", bodyMetrics);
@@ -76,12 +77,12 @@ public class ProfileComputeServiceImpl implements ProfileComputeService {
         // upsert history
         LambdaQueryWrapper<ProfileHistory> hqw = new LambdaQueryWrapper<>();
         hqw.eq(ProfileHistory::getMemberId, memberId)
-           .eq(ProfileHistory::getSnapshotDate, new java.sql.Date(now.atStartOfDay().toEpochDay() * 86400000));
+           .eq(ProfileHistory::getSnapshotDate, java.sql.Date.valueOf(now));
         ProfileHistory history = historyMapper.selectOne(hqw);
         if (history == null) {
             history = new ProfileHistory();
             history.setMemberId(memberId);
-            history.setSnapshotDate(new java.sql.Date(now.atStartOfDay().toEpochDay() * 86400000));
+            history.setSnapshotDate(java.sql.Date.valueOf(now));
             history.setAllMetrics(allMetrics.toJSONString());
             historyMapper.insert(history);
         } else {
@@ -295,24 +296,24 @@ public class ProfileComputeServiceImpl implements ProfileComputeService {
 
         // 身维度
         int bodyScore = 50;
-        double sleepAvg = body.getDoubleValue("sleep_dur_avg", 0);
+        double sleepAvg = body.getDoubleValue("sleep_dur_avg");
         if (sleepAvg >= 8 && sleepAvg <= 10) bodyScore += 15;
         else if (sleepAvg >= 7 && sleepAvg <= 11) bodyScore += 8;
-        int exerciseWeek = body.getIntValue("exercise_count_week", 0);
+        int exerciseWeek = body.getInteger("exercise_count_week") != null ? body.getInteger("exercise_count_week") : 0;
         if (exerciseWeek >= 3) bodyScore += 15;
         else if (exerciseWeek >= 1) bodyScore += 8;
-        int waterAvg = body.getIntValue("water_intake_avg_ml", 0);
+        int waterAvg = body.getInteger("water_intake_avg_ml") != null ? body.getInteger("water_intake_avg_ml") : 0;
         if (waterAvg >= 1000) bodyScore += 10;
         else if (waterAvg >= 500) bodyScore += 5;
         scores.put("body", Math.min(bodyScore, 100));
 
         // 心维度
         int mindScore = 50;
-        double joyRatio = mind.getDoubleValue("emotion_joy_ratio", 0.5);
+        double joyRatio = mind.getDoubleValue("emotion_joy_ratio");
         mindScore += (int) (joyRatio * 30);
-        double stressAvg = mind.getDoubleValue("stress_avg", 5);
+        double stressAvg = mind.getDoubleValue("stress_avg");
         mindScore -= (int) ((stressAvg - 3) * 5);
-        double negativeRatio = mind.getDoubleValue("negative_ratio", 0);
+        double negativeRatio = mind.getDoubleValue("negative_ratio");
         mindScore -= (int) (negativeRatio * 30);
         scores.put("mind", Math.max(0, Math.min(100, mindScore)));
 
@@ -326,17 +327,17 @@ public class ProfileComputeServiceImpl implements ProfileComputeService {
 
         // 行维度
         int actionScore = 50;
-        double compRate = action.getDoubleValue("task_completion_rate", 0.5);
+        double compRate = action.getDoubleValue("task_completion_rate");
         actionScore += (int) (compRate * 30);
-        int streak = action.getIntValue("checkin_streak", 0);
+        int streak = action.getInteger("checkin_streak") != null ? action.getInteger("checkin_streak") : 0;
         actionScore += Math.min(streak, 10);
         scores.put("action", Math.min(100, actionScore));
 
         // 富维度
         int wealthScore = 50;
-        double savingsRate = wealth.getDoubleValue("savings_rate", 0.5);
+        double savingsRate = wealth.getDoubleValue("savings_rate");
         wealthScore += (int) (savingsRate * 30);
-        int financeCount = wealth.getIntValue("income_count", 0) + wealth.getIntValue("expense_count", 0);
+        Integer inc = wealth.getInteger("income_count"); Integer exp = wealth.getInteger("expense_count"); int financeCount = (inc != null ? inc : 0) + (exp != null ? exp : 0);
         if (financeCount >= 5) wealthScore += 20;
         else if (financeCount >= 2) wealthScore += 10;
         scores.put("wealth", Math.min(100, wealthScore));