diff --git a/src/main/java/com/campus/water/controller/StudentDrinkStatsController.java b/src/main/java/com/campus/water/controller/StudentDrinkStatsController.java new file mode 100644 index 0000000..5ffc43c --- /dev/null +++ b/src/main/java/com/campus/water/controller/StudentDrinkStatsController.java @@ -0,0 +1,56 @@ +package com.campus.water.controller; + +import com.campus.water.entity.dto.request.StudentDrinkQueryDTO; +import com.campus.water.entity.vo.StudentDrinkStatsVO; +import com.campus.water.service.StudentDrinkStatsService; +import com.campus.water.util.ResultVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 学生饮水量统计接口 + */ +@RestController +@RequestMapping("/api/student/drink-stats") +@RequiredArgsConstructor +@Tag(name = "学生端-饮水量统计", description = "学生查看本日/本周/本月饮水量") +public class StudentDrinkStatsController { + + private final StudentDrinkStatsService drinkStatsService; + + @PostMapping("/today") + @Operation(summary = "查询本日饮水量", description = "获取学生当日的饮水量、次数及明细") + public ResultVO getTodayStats(@RequestBody StudentDrinkQueryDTO request) { + // 手动校验学生ID非空 + if (request.getStudentId() == null || request.getStudentId().trim().isEmpty()) { + return ResultVO.badRequest("学生ID不能为空"); + } + StudentDrinkStatsVO stats = drinkStatsService.getTodayDrinkStats(request.getStudentId()); + return ResultVO.success(stats, "查询本日饮水量成功"); + } + + @PostMapping("/this-week") + @Operation(summary = "查询本周饮水量", description = "获取学生本周的饮水量、日均量及每日明细") + public ResultVO getThisWeekStats(@RequestBody StudentDrinkQueryDTO request) { + if (request.getStudentId() == null || request.getStudentId().trim().isEmpty()) { + return ResultVO.badRequest("学生ID不能为空"); + } + StudentDrinkStatsVO stats = drinkStatsService.getThisWeekDrinkStats(request.getStudentId()); + return ResultVO.success(stats, "查询本周饮水量成功"); + } + + @PostMapping("/this-month") + @Operation(summary = "查询本月饮水量", description = "获取学生本月的饮水量、日均量及每日明细") + public ResultVO getThisMonthStats(@RequestBody StudentDrinkQueryDTO request) { + if (request.getStudentId() == null || request.getStudentId().trim().isEmpty()) { + return ResultVO.badRequest("学生ID不能为空"); + } + StudentDrinkStatsVO stats = drinkStatsService.getThisMonthDrinkStats(request.getStudentId()); + return ResultVO.success(stats, "查询本月饮水量成功"); + } +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/dto/request/StudentDrinkQueryDTO.java b/src/main/java/com/campus/water/entity/dto/request/StudentDrinkQueryDTO.java new file mode 100644 index 0000000..fbad794 --- /dev/null +++ b/src/main/java/com/campus/water/entity/dto/request/StudentDrinkQueryDTO.java @@ -0,0 +1,12 @@ +package com.campus.water.entity.dto.request; + +import lombok.Data; + +/** + * 学生饮水量查询请求DTO + */ +@Data +public class StudentDrinkQueryDTO { + /** 学生ID */ + private String studentId; +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/vo/DailyDrinkVO.java b/src/main/java/com/campus/water/entity/vo/DailyDrinkVO.java new file mode 100644 index 0000000..16890f3 --- /dev/null +++ b/src/main/java/com/campus/water/entity/vo/DailyDrinkVO.java @@ -0,0 +1,16 @@ +package com.campus.water.entity.vo; + +import lombok.Data; + +/** + * 每日饮水量明细VO + */ +@Data +public class DailyDrinkVO { + /** 日期(yyyy-MM-dd) */ + private String date; + /** 当日饮水量(升) */ + private Double consumption; + /** 当日饮水次数 */ + private Integer count; +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/vo/StudentDrinkStatsVO.java b/src/main/java/com/campus/water/entity/vo/StudentDrinkStatsVO.java new file mode 100644 index 0000000..a09fecc --- /dev/null +++ b/src/main/java/com/campus/water/entity/vo/StudentDrinkStatsVO.java @@ -0,0 +1,28 @@ +package com.campus.water.entity.vo; + +import lombok.Data; +import java.util.List; +import com.campus.water.entity.DrinkRecord; +import com.campus.water.entity.vo.DailyDrinkVO; +/** + * 学生饮水量统计结果VO + */ +@Data +public class StudentDrinkStatsVO { + /** 学生ID */ + private String studentId; + /** 统计维度(本日/本周/本月) */ + private String timeDimension; + /** 统计时间范围(如"2025-12-25~2025-12-25") */ + private String timeRange; + /** 总饮水量(升) */ + private Double totalConsumption; + /** 日均饮水量(升) */ + private Double avgDailyConsumption; + /** 饮水次数 */ + private Integer drinkCount; + /** 按日期分组的每日饮水量明细 */ + private List dailyDetails; + /** 所有饮水记录明细 */ + private List drinkRecords; +} diff --git a/src/main/java/com/campus/water/service/StudentDrinkStatsService.java b/src/main/java/com/campus/water/service/StudentDrinkStatsService.java new file mode 100644 index 0000000..5d01331 --- /dev/null +++ b/src/main/java/com/campus/water/service/StudentDrinkStatsService.java @@ -0,0 +1,105 @@ +package com.campus.water.service; + +import com.campus.water.entity.DrinkRecord; +import com.campus.water.entity.vo.DailyDrinkVO; +import com.campus.water.entity.vo.StudentDrinkStatsVO; +import com.campus.water.mapper.DrinkRecordRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.time.*; +import java.time.temporal.TemporalAdjusters; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 学生饮水量统计服务 + */ +@Service +@RequiredArgsConstructor +public class StudentDrinkStatsService { + + private final DrinkRecordRepository drinkRecordRepository; + + /** + * 查询学生本日饮水量统计 + */ + public StudentDrinkStatsVO getTodayDrinkStats(String studentId) { + LocalDate today = LocalDate.now(); + LocalDateTime start = today.atStartOfDay(); + LocalDateTime end = LocalDateTime.now(); + return calculateStats(studentId, start, end, "本日", "today"); + } + + /** + * 查询学生本周饮水量统计 + */ + public StudentDrinkStatsVO getThisWeekDrinkStats(String studentId) { + LocalDate today = LocalDate.now(); + LocalDateTime start = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).atStartOfDay(); + LocalDateTime end = LocalDateTime.now(); + return calculateStats(studentId, start, end, "本周", "thisWeek"); + } + + /** + * 查询学生本月饮水量统计 + */ + public StudentDrinkStatsVO getThisMonthDrinkStats(String studentId) { + LocalDate today = LocalDate.now(); + LocalDateTime start = today.with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay(); + LocalDateTime end = LocalDateTime.now(); + return calculateStats(studentId, start, end, "本月", "thisMonth"); + } + + /** + * 核心统计逻辑 + */ + private StudentDrinkStatsVO calculateStats(String studentId, LocalDateTime start, LocalDateTime end, + String timeRangeDesc, String timeDimension) { + // 1. 查询时间范围内的饮水记录 + List records = drinkRecordRepository + .findByStudentIdAndDrinkTimeBetweenOrdered(studentId, start, end); + + // 2. 按日期分组统计 + Map> dailyGroup = records.stream() + .collect(Collectors.groupingBy(record -> record.getDrinkTime().toLocalDate())); + + // 3. 构建每日明细 + List dailyDetails = new ArrayList<>(); + dailyGroup.forEach((date, dailyRecords) -> { + DailyDrinkVO dailyVO = new DailyDrinkVO(); + dailyVO.setDate(date.toString()); + // 当日总饮水量 + double dailyTotal = dailyRecords.stream() + .map(DrinkRecord::getWaterConsumption) + .filter(Objects::nonNull) + .mapToDouble(BigDecimal::doubleValue) + .sum(); + dailyVO.setConsumption(dailyTotal); + dailyVO.setCount(dailyRecords.size()); + dailyDetails.add(dailyVO); + }); + // 按日期排序 + dailyDetails.sort(Comparator.comparing(DailyDrinkVO::getDate)); + + // 4. 计算总饮水量、总次数、日均饮水量 + double totalConsumption = dailyDetails.stream() + .mapToDouble(DailyDrinkVO::getConsumption) + .sum(); + int totalCount = records.size(); + double avgDaily = dailyDetails.isEmpty() ? 0 : totalConsumption / dailyDetails.size(); + + // 5. 封装结果VO + StudentDrinkStatsVO statsVO = new StudentDrinkStatsVO(); + statsVO.setStudentId(studentId); + statsVO.setTimeDimension(timeDimension); + statsVO.setTimeRange(timeRangeDesc + "(" + start.toLocalDate() + "~" + end.toLocalDate() + ")"); + statsVO.setTotalConsumption(totalConsumption); + statsVO.setDrinkCount(totalCount); + statsVO.setAvgDailyConsumption(avgDaily); + statsVO.setDailyDetails(dailyDetails); + statsVO.setDrinkRecords(records); + + return statsVO; + } +} \ No newline at end of file