package com.ruoyi.system.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.system.model.TAppUserVisit; import com.ruoyi.system.model.TBigGiftPackageOrder; import com.ruoyi.system.model.TPhysicalGoodsOrder; import com.ruoyi.system.query.DashboardStatisticsQuery; import com.ruoyi.system.service.DashboardStatisticsService; import com.ruoyi.system.service.TAppUserVisitService; import com.ruoyi.system.service.TBigGiftPackageOrderService; import com.ruoyi.system.service.TPhysicalGoodsOrderService; import com.ruoyi.system.vo.*; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; /** * 数据统计面板 服务实现类 * * @author xiaochen * @since 2025-12-23 */ @Service @RequiredArgsConstructor(onConstructor_ = @Lazy) public class DashboardStatisticsServiceImpl implements DashboardStatisticsService { private final TAppUserVisitService appUserVisitService; private final TPhysicalGoodsOrderService physicalGoodsOrderService; private final TBigGiftPackageOrderService bigGiftPackageOrderService; /** * 实物商品订单有效状态:完成支付(2)、确认收货(3) */ private static final List PHYSICAL_ORDER_VALID_STATUS = Arrays.asList(2, 3); /** * 大礼包订单有效状态:已付订金(2)、完成支付(3)、确认收货(4) */ private static final List BIG_GIFT_ORDER_VALID_STATUS = Arrays.asList(2, 3, 4); @Override public DashboardStatisticsVO getStatistics(DashboardStatisticsQuery query) { // 处理日期范围,默认为当月 LocalDate endDate = query.getEndDate() != null ? query.getEndDate() : LocalDate.now(); LocalDate startDate = query.getStartDate() != null ? query.getStartDate() : endDate.withDayOfMonth(1); // 计算上一周期的日期范围(用于环比计算) long daysBetween = ChronoUnit.DAYS.between(startDate, endDate) + 1; LocalDate lastPeriodEnd = startDate.minusDays(1); LocalDate lastPeriodStart = lastPeriodEnd.minusDays(daysBetween - 1); return DashboardStatisticsVO.builder() .visitStats(buildVisitStats(startDate, endDate, lastPeriodStart, lastPeriodEnd)) .orderCountStats(buildOrderCountStats(startDate, endDate, lastPeriodStart, lastPeriodEnd)) .orderAmountStats(buildOrderAmountStats(startDate, endDate, lastPeriodStart, lastPeriodEnd)) .monthlyStats(buildMonthlyStats(startDate, endDate)) .build(); } /** * 构建客户访问量统计 */ private StatisticsItemVO buildVisitStats(LocalDate startDate, LocalDate endDate, LocalDate lastPeriodStart, LocalDate lastPeriodEnd) { // 当前周期访问量 long currentTotal = appUserVisitService.count(new LambdaQueryWrapper() .between(TAppUserVisit::getVisitDate, startDate, endDate)); // 上一周期访问量 long lastTotal = appUserVisitService.count(new LambdaQueryWrapper() .between(TAppUserVisit::getVisitDate, lastPeriodStart, lastPeriodEnd)); // 趋势数据(按日统计) List visitList = appUserVisitService.list(new LambdaQueryWrapper() .between(TAppUserVisit::getVisitDate, startDate, endDate)); Map visitByDate = visitList.stream() .collect(Collectors.groupingBy(TAppUserVisit::getVisitDate, Collectors.counting())); List trend = buildDailyTrend(startDate, endDate, visitByDate); return StatisticsItemVO.builder() .total(BigDecimal.valueOf(currentTotal)) .rate(calculateRate(currentTotal, lastTotal)) .trend(trend) .build(); } /** * 构建订单数统计 */ private StatisticsItemVO buildOrderCountStats(LocalDate startDate, LocalDate endDate, LocalDate lastPeriodStart, LocalDate lastPeriodEnd) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); LocalDateTime lastStartDateTime = lastPeriodStart.atStartOfDay(); LocalDateTime lastEndDateTime = lastPeriodEnd.atTime(LocalTime.MAX); // 当前周期订单数 long physicalCount = physicalGoodsOrderService.count(new LambdaQueryWrapper() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime)); long bigGiftCount = bigGiftPackageOrderService.count(new LambdaQueryWrapper() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime)); long currentTotal = physicalCount + bigGiftCount; // 上一周期订单数 long lastPhysicalCount = physicalGoodsOrderService.count(new LambdaQueryWrapper() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, lastStartDateTime, lastEndDateTime)); long lastBigGiftCount = bigGiftPackageOrderService.count(new LambdaQueryWrapper() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, lastStartDateTime, lastEndDateTime)); long lastTotal = lastPhysicalCount + lastBigGiftCount; // 趋势数据 List trend = buildOrderCountTrend(startDate, endDate); return StatisticsItemVO.builder() .total(BigDecimal.valueOf(currentTotal)) .rate(calculateRate(currentTotal, lastTotal)) .trend(trend) .build(); } /** * 构建订单金额统计 */ private StatisticsItemVO buildOrderAmountStats(LocalDate startDate, LocalDate endDate, LocalDate lastPeriodStart, LocalDate lastPeriodEnd) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); LocalDateTime lastStartDateTime = lastPeriodStart.atStartOfDay(); LocalDateTime lastEndDateTime = lastPeriodEnd.atTime(LocalTime.MAX); // 当前周期订单金额 BigDecimal currentTotal = calculateTotalOrderAmount(startDateTime, endDateTime); // 上一周期订单金额 BigDecimal lastTotal = calculateTotalOrderAmount(lastStartDateTime, lastEndDateTime); // 趋势数据 List trend = buildOrderAmountTrend(startDate, endDate); return StatisticsItemVO.builder() .total(currentTotal) .rate(calculateRate(currentTotal, lastTotal)) .trend(trend) .build(); } /** * 计算指定时间范围内的订单总金额 */ private BigDecimal calculateTotalOrderAmount(LocalDateTime startDateTime, LocalDateTime endDateTime) { // 实物商品订单金额 List physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime) .select(TPhysicalGoodsOrder::getPaymentPrice)); BigDecimal physicalAmount = physicalOrders.stream() .map(o -> o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); // 大礼包订单金额 List bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime) .select(TBigGiftPackageOrder::getPaymentPrice)); BigDecimal bigGiftAmount = bigGiftOrders.stream() .map(o -> o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); return physicalAmount.add(bigGiftAmount); } /** * 构建月度订单统计 */ private List buildMonthlyStats(LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); // 获取实物商品订单 List physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime)); // 获取大礼包订单 List bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime)); // 按月份分组统计 Map countByMonth = new TreeMap<>(); Map amountByMonth = new TreeMap<>(); // 统计实物商品订单 for (TPhysicalGoodsOrder order : physicalOrders) { String month = order.getCreateTime().getMonthValue() + "月"; countByMonth.merge(month, 1L, Long::sum); BigDecimal amount = order.getPaymentPrice() != null ? order.getPaymentPrice() : BigDecimal.ZERO; amountByMonth.merge(month, amount, BigDecimal::add); } // 统计大礼包订单 for (TBigGiftPackageOrder order : bigGiftOrders) { String month = order.getCreateTime().getMonthValue() + "月"; countByMonth.merge(month, 1L, Long::sum); BigDecimal amount = order.getPaymentPrice() != null ? order.getPaymentPrice() : BigDecimal.ZERO; amountByMonth.merge(month, amount, BigDecimal::add); } // 构建返回结果 List result = new ArrayList<>(); for (String month : countByMonth.keySet()) { result.add(new MonthlyOrderStatisticsVO( month, countByMonth.getOrDefault(month, 0L), amountByMonth.getOrDefault(month, BigDecimal.ZERO) )); } // 按月份数字排序 result.sort(Comparator.comparingInt(vo -> Integer.parseInt(vo.getMonth().replace("月", "")))); return result; } /** * 构建每日趋势数据 */ private List buildDailyTrend(LocalDate startDate, LocalDate endDate, Map dataByDate) { List trend = new ArrayList<>(); LocalDate current = startDate; while (!current.isAfter(endDate)) { Long value = dataByDate.getOrDefault(current, 0L); trend.add(new TrendDataVO(current.toString(), value)); current = current.plusDays(1); } return trend; } /** * 构建订单数趋势数据(优化:一次查询,内存分组) */ private List buildOrderCountTrend(LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); // 一次性查询所有实物商品订单 List physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime) .select(TPhysicalGoodsOrder::getCreateTime)); // 一次性查询所有大礼包订单 List bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime) .select(TBigGiftPackageOrder::getCreateTime)); // 按日期分组统计 Map countByDate = new HashMap<>(); physicalOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); countByDate.merge(date, 1L, Long::sum); }); bigGiftOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); countByDate.merge(date, 1L, Long::sum); }); // 构建趋势数据 return buildDailyTrend(startDate, endDate, countByDate); } /** * 构建订单金额趋势数据(优化:一次查询,内存分组) */ private List buildOrderAmountTrend(LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); // 一次性查询所有实物商品订单 List physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime) .select(TPhysicalGoodsOrder::getCreateTime, TPhysicalGoodsOrder::getPaymentPrice)); // 一次性查询所有大礼包订单 List bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime) .select(TBigGiftPackageOrder::getCreateTime, TBigGiftPackageOrder::getPaymentPrice)); // 按日期分组统计金额 Map amountByDate = new HashMap<>(); physicalOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); BigDecimal amount = o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO; amountByDate.merge(date, amount, BigDecimal::add); }); bigGiftOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); BigDecimal amount = o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO; amountByDate.merge(date, amount, BigDecimal::add); }); // 构建趋势数据 List trend = new ArrayList<>(); LocalDate current = startDate; while (!current.isAfter(endDate)) { BigDecimal value = amountByDate.getOrDefault(current, BigDecimal.ZERO); trend.add(new TrendDataVO(current.toString(), value)); current = current.plusDays(1); } return trend; } /** * 计算环比增长率 * * @param current 当前值 * @param last 上期值 * @return 环比增长率(百分比) */ private BigDecimal calculateRate(long current, long last) { if (last == 0) { return current > 0 ? BigDecimal.valueOf(100) : BigDecimal.ZERO; } return BigDecimal.valueOf(current - last) .multiply(BigDecimal.valueOf(100)) .divide(BigDecimal.valueOf(last), 2, RoundingMode.HALF_UP); } /** * 计算环比增长率(BigDecimal版本) */ private BigDecimal calculateRate(BigDecimal current, BigDecimal last) { if (last.compareTo(BigDecimal.ZERO) == 0) { return current.compareTo(BigDecimal.ZERO) > 0 ? BigDecimal.valueOf(100) : BigDecimal.ZERO; } return current.subtract(last) .multiply(BigDecimal.valueOf(100)) .divide(last, 2, RoundingMode.HALF_UP); } }