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

feat(TASK-013~015): 实现任务提醒和统计功能

- TASK-013: 任务提醒通知(即将到期/超时任务查询)
- TASK-014: 任务历史记录(带状态/时间筛选)
- TASK-015: 任务统计分析(概览/分类/日期/家庭统计)

新增API:
- GET /api/tasks/reminder/{childId} - 需要提醒的任务
- GET /api/tasks/upcoming/{childId} - 即将到期任务
- GET /api/tasks/overdue/{childId} - 已超时任务
- GET /api/tasks/history/filter/{childId} - 带筛选的历史
- GET /api/tasks/statistics/{childId} - 统计概览
- GET /api/tasks/statistics/category/{childId} - 分类统计
- GET /api/tasks/statistics/date/{childId} - 日期统计
- GET /api/tasks/statistics/family/{familyId} - 家庭统计
User 5 месяцев назад
Родитель
Сommit
e9c4180f33

+ 119 - 0
zxyj-backend/src/main/java/com/zxyj/controller/task/TaskReminderController.java

@@ -0,0 +1,119 @@
+package com.zxyj.controller.task;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.Task;
+import com.zxyj.service.TaskReminderService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TASK-013~015: 任务提醒和统计接口
+ */
+@Tag(name = "任务提醒统计", description = "任务提醒、历史查询、统计分析接口")
+@RestController
+@RequestMapping("/api/tasks")
+public class TaskReminderController {
+
+    @Resource
+    private TaskReminderService taskReminderService;
+
+    /**
+     * TASK-013: 获取需要提醒的任务(30分钟内到期)
+     */
+    @Operation(summary = "获取需要提醒的任务")
+    @GetMapping("/reminder/{childId}")
+    public Result<List<Task>> getTasksNeedReminder(@PathVariable Long childId) {
+        List<Task> tasks = taskReminderService.getTasksNeedReminder(childId);
+        return Result.success(tasks);
+    }
+
+    /**
+     * TASK-013: 获取即将到期的任务(可指定时间范围)
+     */
+    @Operation(summary = "获取即将到期的任务")
+    @GetMapping("/upcoming/{childId}")
+    public Result<List<Task>> getUpcomingTasks(@PathVariable Long childId,
+                                                @RequestParam(defaultValue = "60") Integer minutes) {
+        List<Task> tasks = taskReminderService.getUpcomingTasks(childId, minutes);
+        return Result.success(tasks);
+    }
+
+    /**
+     * TASK-013: 获取已超时任务
+     */
+    @Operation(summary = "获取已超时任务")
+    @GetMapping("/overdue/{childId}")
+    public Result<List<Task>> getOverdueTasks(@PathVariable Long childId) {
+        List<Task> tasks = taskReminderService.getOverdueTasks(childId);
+        return Result.success(tasks);
+    }
+
+    /**
+     * TASK-014: 获取任务历史(带筛选)
+     */
+    @Operation(summary = "获取任务历史(带筛选)")
+    @GetMapping("/history/filter/{childId}")
+    public Result<Map<String, Object>> getTaskHistoryWithFilter(
+            @PathVariable Long childId,
+            @RequestParam(required = false) String status,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer size) {
+        Map<String, Object> result = taskReminderService.getTaskHistoryWithFilter(
+            childId, status, startDate, endDate, page, size);
+        return Result.success(result);
+    }
+
+    /**
+     * TASK-015: 获取任务统计概览
+     */
+    @Operation(summary = "获取任务统计概览")
+    @GetMapping("/statistics/{childId}")
+    public Result<Map<String, Object>> getTaskStatistics(
+            @PathVariable Long childId,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
+        Map<String, Object> stats = taskReminderService.getTaskStatistics(childId, startDate, endDate);
+        return Result.success(stats);
+    }
+
+    /**
+     * TASK-015: 获取按分类统计
+     */
+    @Operation(summary = "获取按分类统计")
+    @GetMapping("/statistics/category/{childId}")
+    public Result<List<Map<String, Object>>> getTaskStatsByCategory(@PathVariable Long childId) {
+        List<Map<String, Object>> stats = taskReminderService.getTaskStatsByCategory(childId);
+        return Result.success(stats);
+    }
+
+    /**
+     * TASK-015: 获取按日期统计
+     */
+    @Operation(summary = "获取按日期统计")
+    @GetMapping("/statistics/date/{childId}")
+    public Result<List<Map<String, Object>>> getTaskStatsByDate(
+            @PathVariable Long childId,
+            @RequestParam(defaultValue = "7") Integer days) {
+        List<Map<String, Object>> stats = taskReminderService.getTaskStatsByDate(childId, days);
+        return Result.success(stats);
+    }
+
+    /**
+     * TASK-015: 获取家庭任务统计
+     */
+    @Operation(summary = "获取家庭任务统计")
+    @GetMapping("/statistics/family/{familyId}")
+    public Result<Map<String, Object>> getFamilyTaskStatistics(@PathVariable Long familyId) {
+        Map<String, Object> stats = taskReminderService.getFamilyTaskStatistics(familyId);
+        return Result.success(stats);
+    }
+}

+ 278 - 0
zxyj-backend/src/main/java/com/zxyj/service/TaskReminderService.java

@@ -0,0 +1,278 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zxyj.entity.Task;
+import com.zxyj.mapper.TaskMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * TASK-013~015: 任务提醒和统计服务
+ */
+@Slf4j
+@Service
+public class TaskReminderService {
+
+    @Resource
+    private TaskMapper taskMapper;
+
+    /**
+     * TASK-013: 获取即将到期的任务(用于提醒)
+     * 返回指定时间范围内即将到期的任务
+     */
+    public List<Task> getUpcomingTasks(Long childId, int minutesBefore) {
+        Date now = new Date();
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.MINUTE, minutesBefore);
+        Date endTime = cal.getTime();
+
+        return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .eq(Task::getStatus, "pending")
+            .ge(Task::getDeadline, now)
+            .le(Task::getDeadline, endTime)
+            .orderByAsc(Task::getDeadline));
+    }
+
+    /**
+     * TASK-013: 获取需要提醒的任务(半小时内到期)
+     */
+    public List<Task> getTasksNeedReminder(Long childId) {
+        return getUpcomingTasks(childId, 30);
+    }
+
+    /**
+     * TASK-013: 获取已超时但未完成的任务
+     */
+    public List<Task> getOverdueTasks(Long childId) {
+        Date now = new Date();
+
+        return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .eq(Task::getStatus, "pending")
+            .lt(Task::getDeadline, now)
+            .orderByAsc(Task::getDeadline));
+    }
+
+    /**
+     * TASK-014: 获取任务历史(带筛选条件)
+     */
+    public Map<String, Object> getTaskHistoryWithFilter(Long childId, String status,
+                                                         Date startDate, Date endDate,
+                                                         Integer page, Integer size) {
+        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Task::getChildId, childId);
+
+        // 状态筛选
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(Task::getStatus, status);
+        }
+
+        // 时间范围
+        if (startDate != null) {
+            wrapper.ge(Task::getCreatedAt, startDate);
+        }
+        if (endDate != null) {
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(endDate);
+            cal.add(Calendar.DAY_OF_MONTH, 1);
+            wrapper.le(Task::getCreatedAt, cal.getTime());
+        }
+
+        // 计算总数
+        Long total = taskMapper.selectCount(wrapper);
+
+        // 分页查询
+        wrapper.orderByDesc(Task::getCreatedAt);
+        int offset = (page - 1) * size;
+        List<Task> tasks = taskMapper.selectList(wrapper);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("list", tasks);
+        result.put("total", total);
+        result.put("page", page);
+        result.put("size", size);
+
+        return result;
+    }
+
+    /**
+     * TASK-015: 获取任务统计概览
+     */
+    public Map<String, Object> getTaskStatistics(Long childId, Date startDate, Date endDate) {
+        Map<String, Object> stats = new HashMap<>();
+
+        // 基础统计
+        LambdaQueryWrapper<Task> baseWrapper = new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId);
+
+        if (startDate != null && endDate != null) {
+            baseWrapper.ge(Task::getCreatedAt, startDate)
+                     .le(Task::getCreatedAt, endDate);
+        }
+
+        // 总任务数
+        Long totalTasks = taskMapper.selectCount(baseWrapper);
+        stats.put("totalTasks", totalTasks);
+
+        // 已完成任务数
+        Long completedTasks = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .eq(Task::getStatus, "completed")
+            .ge(startDate != null, Task::getCompletedAt, startDate)
+            .le(endDate != null, Task::getCompletedAt, endDate));
+        stats.put("completedTasks", completedTasks);
+
+        // 待完成任务数
+        Long pendingTasks = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .eq(Task::getStatus, "pending"));
+        stats.put("pendingTasks", pendingTasks);
+
+        // 超时任务数
+        Long overdueTasks = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .eq(Task::getStatus, "pending")
+            .lt(Task::getDeadline, new Date()));
+        stats.put("overdueTasks", overdueTasks);
+
+        // 已取消任务数
+        Long cancelledTasks = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .eq(Task::getStatus, "cancelled"));
+        stats.put("cancelledTasks", cancelledTasks);
+
+        // 完成率
+        double completionRate = totalTasks > 0 ? (double) completedTasks / totalTasks * 100 : 0;
+        stats.put("completionRate", Math.round(completionRate * 100.0) / 100.0);
+
+        return stats;
+    }
+
+    /**
+     * TASK-015: 获取按分类统计
+     */
+    public List<Map<String, Object>> getTaskStatsByCategory(Long childId) {
+        List<Map<String, Object>> result = new ArrayList<>();
+
+        // 获取所有任务按分类统计
+        List<Task> allTasks = taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getChildId, childId)
+            .isNotNull(Task::getCategory));
+
+        // 按分类分组统计
+        Map<String, Integer> categoryCount = new HashMap<>();
+        Map<String, Integer> categoryCompleted = new HashMap<>();
+
+        for (Task task : allTasks) {
+            String category = task.getCategory();
+            if (category != null) {
+                categoryCount.merge(category, 1, Integer::sum);
+                if ("completed".equals(task.getStatus())) {
+                    categoryCompleted.merge(category, 1, Integer::sum);
+                }
+            }
+        }
+
+        // 构建结果
+        for (Map.Entry<String, Integer> entry : categoryCount.entrySet()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("category", entry.getKey());
+            item.put("total", entry.getValue());
+            item.put("completed", categoryCompleted.getOrDefault(entry.getKey(), 0));
+            double rate = entry.getValue() > 0 ?
+                (double) categoryCompleted.getOrDefault(entry.getKey(), 0) / entry.getValue() * 100 : 0;
+            item.put("completionRate", Math.round(rate * 100.0) / 100.0);
+            result.add(item);
+        }
+
+        return result;
+    }
+
+    /**
+     * TASK-015: 获取按日期统计(最近7天/30天)
+     */
+    public List<Map<String, Object>> getTaskStatsByDate(Long childId, int days) {
+        List<Map<String, Object>> result = new ArrayList<>();
+        Calendar cal = Calendar.getInstance();
+
+        for (int i = days - 1; i >= 0; i--) {
+            cal.setTime(new Date());
+            cal.add(Calendar.DAY_OF_MONTH, -i);
+            cal.set(Calendar.HOUR_OF_DAY, 0);
+            cal.set(Calendar.MINUTE, 0);
+            cal.set(Calendar.SECOND, 0);
+            cal.set(Calendar.MILLISECOND, 0);
+            Date startOfDay = cal.getTime();
+
+            cal.add(Calendar.DAY_OF_MONTH, 1);
+            Date endOfDay = cal.getTime();
+
+            // 当天创建的任务数
+            Long created = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+                .eq(Task::getChildId, childId)
+                .ge(Task::getCreatedAt, startOfDay)
+                .lt(Task::getCreatedAt, endOfDay));
+
+            // 当天完成的任务数
+            Long completed = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+                .eq(Task::getChildId, childId)
+                .eq(Task::getStatus, "completed")
+                .ge(Task::getCompletedAt, startOfDay)
+                .lt(Task::getCompletedAt, endOfDay));
+
+            Map<String, Object> dayStats = new HashMap<>();
+            dayStats.put("date", startOfDay);
+            dayStats.put("created", created);
+            dayStats.put("completed", completed);
+            result.add(dayStats);
+        }
+
+        return result;
+    }
+
+    /**
+     * TASK-015: 获取家庭任务统计
+     */
+    public Map<String, Object> getFamilyTaskStatistics(Long familyId) {
+        Map<String, Object> stats = new HashMap<>();
+
+        // 家庭总任务数
+        Long totalTasks = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId));
+        stats.put("totalTasks", totalTasks);
+
+        // 今日任务数
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        Date startOfDay = cal.getTime();
+        cal.add(Calendar.DAY_OF_MONTH, 1);
+        Date endOfDay = cal.getTime();
+
+        Long todayTasks = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId)
+            .ge(Task::getDeadline, startOfDay)
+            .lt(Task::getDeadline, endOfDay));
+        stats.put("todayTasks", todayTasks);
+
+        // 今日已完成
+        Long todayCompleted = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId)
+            .eq(Task::getStatus, "completed")
+            .ge(Task::getCompletedAt, startOfDay)
+            .lt(Task::getCompletedAt, endOfDay));
+        stats.put("todayCompleted", todayCompleted);
+
+        // 今日完成率
+        double todayRate = todayTasks > 0 ? (double) todayCompleted / todayTasks * 100 : 0;
+        stats.put("todayCompletionRate", Math.round(todayRate * 100.0) / 100.0);
+
+        return stats;
+    }
+}