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<Integer> PHYSICAL_ORDER_VALID_STATUS = Arrays.asList(2, 3);
|
|
/**
|
* 大礼包订单有效状态:已付订金(2)、完成支付(3)、确认收货(4)
|
*/
|
private static final List<Integer> 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<TAppUserVisit>()
|
.between(TAppUserVisit::getVisitDate, startDate, endDate));
|
|
// 上一周期访问量
|
long lastTotal = appUserVisitService.count(new LambdaQueryWrapper<TAppUserVisit>()
|
.between(TAppUserVisit::getVisitDate, lastPeriodStart, lastPeriodEnd));
|
|
// 趋势数据(按日统计)
|
List<TAppUserVisit> visitList = appUserVisitService.list(new LambdaQueryWrapper<TAppUserVisit>()
|
.between(TAppUserVisit::getVisitDate, startDate, endDate));
|
|
Map<LocalDate, Long> visitByDate = visitList.stream()
|
.collect(Collectors.groupingBy(TAppUserVisit::getVisitDate, Collectors.counting()));
|
|
List<TrendDataVO> 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<TPhysicalGoodsOrder>()
|
.in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS)
|
.between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime));
|
|
long bigGiftCount = bigGiftPackageOrderService.count(new LambdaQueryWrapper<TBigGiftPackageOrder>()
|
.in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS)
|
.between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime));
|
|
long currentTotal = physicalCount + bigGiftCount;
|
|
// 上一周期订单数
|
long lastPhysicalCount = physicalGoodsOrderService.count(new LambdaQueryWrapper<TPhysicalGoodsOrder>()
|
.in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS)
|
.between(TPhysicalGoodsOrder::getCreateTime, lastStartDateTime, lastEndDateTime));
|
|
long lastBigGiftCount = bigGiftPackageOrderService.count(new LambdaQueryWrapper<TBigGiftPackageOrder>()
|
.in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS)
|
.between(TBigGiftPackageOrder::getCreateTime, lastStartDateTime, lastEndDateTime));
|
|
long lastTotal = lastPhysicalCount + lastBigGiftCount;
|
|
// 趋势数据
|
List<TrendDataVO> 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<TrendDataVO> trend = buildOrderAmountTrend(startDate, endDate);
|
|
return StatisticsItemVO.builder()
|
.total(currentTotal)
|
.rate(calculateRate(currentTotal, lastTotal))
|
.trend(trend)
|
.build();
|
}
|
|
/**
|
* 计算指定时间范围内的订单总金额
|
*/
|
private BigDecimal calculateTotalOrderAmount(LocalDateTime startDateTime, LocalDateTime endDateTime) {
|
// 实物商品订单金额
|
List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list(
|
new LambdaQueryWrapper<TPhysicalGoodsOrder>()
|
.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<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list(
|
new LambdaQueryWrapper<TBigGiftPackageOrder>()
|
.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<MonthlyOrderStatisticsVO> buildMonthlyStats(LocalDate startDate, LocalDate endDate) {
|
LocalDateTime startDateTime = startDate.atStartOfDay();
|
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
|
|
// 获取实物商品订单
|
List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list(
|
new LambdaQueryWrapper<TPhysicalGoodsOrder>()
|
.in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS)
|
.between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime));
|
|
// 获取大礼包订单
|
List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list(
|
new LambdaQueryWrapper<TBigGiftPackageOrder>()
|
.in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS)
|
.between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime));
|
|
// 按月份分组统计
|
Map<String, Long> countByMonth = new TreeMap<>();
|
Map<String, BigDecimal> 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<MonthlyOrderStatisticsVO> 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<TrendDataVO> buildDailyTrend(LocalDate startDate, LocalDate endDate,
|
Map<LocalDate, Long> dataByDate) {
|
List<TrendDataVO> 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<TrendDataVO> buildOrderCountTrend(LocalDate startDate, LocalDate endDate) {
|
LocalDateTime startDateTime = startDate.atStartOfDay();
|
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
|
|
// 一次性查询所有实物商品订单
|
List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list(
|
new LambdaQueryWrapper<TPhysicalGoodsOrder>()
|
.in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS)
|
.between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime)
|
.select(TPhysicalGoodsOrder::getCreateTime));
|
|
// 一次性查询所有大礼包订单
|
List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list(
|
new LambdaQueryWrapper<TBigGiftPackageOrder>()
|
.in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS)
|
.between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime)
|
.select(TBigGiftPackageOrder::getCreateTime));
|
|
// 按日期分组统计
|
Map<LocalDate, Long> 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<TrendDataVO> buildOrderAmountTrend(LocalDate startDate, LocalDate endDate) {
|
LocalDateTime startDateTime = startDate.atStartOfDay();
|
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
|
|
// 一次性查询所有实物商品订单
|
List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list(
|
new LambdaQueryWrapper<TPhysicalGoodsOrder>()
|
.in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS)
|
.between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime)
|
.select(TPhysicalGoodsOrder::getCreateTime, TPhysicalGoodsOrder::getPaymentPrice));
|
|
// 一次性查询所有大礼包订单
|
List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list(
|
new LambdaQueryWrapper<TBigGiftPackageOrder>()
|
.in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS)
|
.between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime)
|
.select(TBigGiftPackageOrder::getCreateTime, TBigGiftPackageOrder::getPaymentPrice));
|
|
// 按日期分组统计金额
|
Map<LocalDate, BigDecimal> 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<TrendDataVO> 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);
|
}
|
|
}
|