| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- package com.haha.admin.task;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.haha.entity.*;
- import com.haha.mapper.*;
- import com.haha.common.constant.OrderConstants;
- import com.haha.common.utils.TraceIdUtils;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.stereotype.Component;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.time.LocalDate;
- import java.time.LocalDateTime;
- import java.time.LocalTime;
- import java.util.*;
- import java.util.stream.Collectors;
- @Slf4j
- @Component
- public class StatisticsTask {
- @Autowired
- private OrderMapper orderMapper;
- @Autowired
- private OrderItemMapper orderItemMapper;
- @Autowired
- private DeviceMapper deviceMapper;
- @Autowired
- private ShopMapper shopMapper;
- @Autowired
- private UserMapper userMapper;
- @Autowired
- private StatCategoryDailyMapper statCategoryDailyMapper;
- @Autowired
- private StatProductDailyMapper statProductDailyMapper;
- @Autowired
- private StatDeviceDailyMapper statDeviceDailyMapper;
- @Autowired
- private StatShopDailyMapper statShopDailyMapper;
- @Autowired
- private StatUserRepurchaseMapper statUserRepurchaseMapper;
- @Scheduled(cron = "0 5 0 * * ?")
- public void aggregateDailyStatistics() {
- // 生成独立的 TraceId 用于日志追踪
- String traceId = TraceIdUtils.generateTraceId();
- TraceIdUtils.setTraceId(traceId);
-
- LocalDate yesterday = LocalDate.now().minusDays(1);
- log.info("开始执行每日统计汇总任务,统计日期:{}", yesterday);
-
- try {
- aggregateCategoryDaily(yesterday);
- aggregateProductDaily(yesterday);
- aggregateDeviceDaily(yesterday);
- aggregateShopDaily(yesterday);
- aggregateUserRepurchase(yesterday);
-
- log.info("每日统计汇总任务执行完成");
- } catch (Exception e) {
- log.error("每日统计汇总任务执行失败", e);
- } finally {
- // 清理 MDC 上下文
- TraceIdUtils.removeTraceId();
- }
- }
- private void aggregateCategoryDaily(LocalDate statDate) {
- LocalDateTime startDateTime = statDate.atStartOfDay();
- LocalDateTime endDateTime = statDate.atTime(LocalTime.MAX);
-
- List<OrderItem> items = orderItemMapper.selectList(
- new LambdaQueryWrapper<OrderItem>()
- .ge(OrderItem::getPayTime, startDateTime)
- .le(OrderItem::getPayTime, endDateTime)
- );
-
- Map<String, List<OrderItem>> categoryMap = items.stream()
- .filter(item -> item.getProductType() != null)
- .collect(Collectors.groupingBy(OrderItem::getProductType));
-
- for (Map.Entry<String, List<OrderItem>> entry : categoryMap.entrySet()) {
- String category = entry.getKey();
- List<OrderItem> categoryItems = entry.getValue();
-
- StatCategoryDaily stat = new StatCategoryDaily();
- stat.setStatDate(statDate);
- stat.setCategory(category);
- stat.setShopId(null);
- stat.setQuantity(categoryItems.stream().mapToInt(i -> i.getQuantity() != null ? i.getQuantity() : 0).sum());
- stat.setOrderCount((int) categoryItems.stream().map(OrderItem::getOrderId).distinct().count());
-
- BigDecimal salesAmount = categoryItems.stream()
- .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setSalesAmount(salesAmount);
-
- BigDecimal costAmount = categoryItems.stream()
- .map(i -> {
- if (i.getCostPrice() != null && i.getQuantity() != null) {
- return i.getCostPrice().multiply(BigDecimal.valueOf(i.getQuantity()));
- }
- return BigDecimal.ZERO;
- })
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setCostAmount(costAmount);
- stat.setProfitAmount(salesAmount.subtract(costAmount));
- stat.setCreateTime(LocalDateTime.now());
-
- statCategoryDailyMapper.insert(stat);
- }
-
- log.info("品类统计汇总完成,共{}条记录", categoryMap.size());
- }
- private void aggregateProductDaily(LocalDate statDate) {
- LocalDateTime startDateTime = statDate.atStartOfDay();
- LocalDateTime endDateTime = statDate.atTime(LocalTime.MAX);
-
- List<OrderItem> items = orderItemMapper.selectList(
- new LambdaQueryWrapper<OrderItem>()
- .ge(OrderItem::getPayTime, startDateTime)
- .le(OrderItem::getPayTime, endDateTime)
- );
-
- Map<Long, List<OrderItem>> productMap = items.stream()
- .filter(item -> item.getProductId() != null)
- .collect(Collectors.groupingBy(OrderItem::getProductId));
-
- int count = 0;
- for (Map.Entry<Long, List<OrderItem>> entry : productMap.entrySet()) {
- List<OrderItem> productItems = entry.getValue();
- OrderItem first = productItems.get(0);
-
- Map<String, List<OrderItem>> deviceGroup = productItems.stream()
- .filter(i -> i.getDeviceId() != null)
- .collect(Collectors.groupingBy(OrderItem::getDeviceId));
-
- if (deviceGroup.isEmpty()) {
- StatProductDaily stat = createProductStat(statDate, first, productItems, null, null);
- statProductDailyMapper.insert(stat);
- count++;
- } else {
- for (Map.Entry<String, List<OrderItem>> deviceEntry : deviceGroup.entrySet()) {
- StatProductDaily stat = createProductStat(statDate, first, deviceEntry.getValue(),
- deviceEntry.getKey(), null);
- statProductDailyMapper.insert(stat);
- count++;
- }
- }
- }
-
- log.info("商品统计汇总完成,共{}条记录", count);
- }
- private StatProductDaily createProductStat(LocalDate statDate, OrderItem first,
- List<OrderItem> items, String deviceId, Long shopId) {
- StatProductDaily stat = new StatProductDaily();
- stat.setStatDate(statDate);
- stat.setProductId(first.getProductId());
- stat.setProductCode(first.getProductCode());
- stat.setProductName(first.getProductName());
- stat.setCategory(first.getProductType());
- stat.setShopId(shopId != null ? shopId : first.getShopId());
- stat.setDeviceId(deviceId);
- stat.setQuantity(items.stream().mapToInt(i -> i.getQuantity() != null ? i.getQuantity() : 0).sum());
- stat.setOrderCount((int) items.stream().map(OrderItem::getOrderId).distinct().count());
-
- BigDecimal salesAmount = items.stream()
- .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setSalesAmount(salesAmount);
-
- BigDecimal costAmount = items.stream()
- .map(i -> {
- if (i.getCostPrice() != null && i.getQuantity() != null) {
- return i.getCostPrice().multiply(BigDecimal.valueOf(i.getQuantity()));
- }
- return BigDecimal.ZERO;
- })
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setCostAmount(costAmount);
- stat.setProfitAmount(salesAmount.subtract(costAmount));
- stat.setCreateTime(LocalDateTime.now());
-
- return stat;
- }
- private void aggregateDeviceDaily(LocalDate statDate) {
- LocalDateTime startDateTime = statDate.atStartOfDay();
- LocalDateTime endDateTime = statDate.atTime(LocalTime.MAX);
-
- List<Order> orders = orderMapper.selectList(
- new LambdaQueryWrapper<Order>()
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .ge(Order::getPayTime, startDateTime)
- .le(Order::getPayTime, endDateTime)
- );
-
- Map<String, List<Order>> deviceOrderMap = orders.stream()
- .filter(o -> o.getDeviceId() != null)
- .collect(Collectors.groupingBy(Order::getDeviceId));
-
- List<Device> devices = deviceMapper.selectList(null);
- Map<String, Device> deviceMap = devices.stream()
- .collect(Collectors.toMap(Device::getDeviceId, d -> d, (a, b) -> a));
-
- int count = 0;
- for (Device device : devices) {
- StatDeviceDaily stat = new StatDeviceDaily();
- stat.setStatDate(statDate);
- stat.setDeviceId(device.getDeviceId());
- stat.setDeviceName(device.getName());
- stat.setShopId(device.getShopId());
-
- if (device.getShopId() != null) {
- Shop shop = shopMapper.selectById(device.getShopId());
- if (shop != null) {
- stat.setShopName(shop.getName());
- }
- }
-
- List<Order> deviceOrders = deviceOrderMap.getOrDefault(device.getDeviceId(), Collections.emptyList());
- stat.setOrderCount(deviceOrders.size());
- stat.setUserCount((int) deviceOrders.stream().map(Order::getUserId).filter(Objects::nonNull).distinct().count());
-
- BigDecimal salesAmount = deviceOrders.stream()
- .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setSalesAmount(salesAmount);
- stat.setCostAmount(BigDecimal.ZERO);
- stat.setProfitAmount(salesAmount);
- stat.setCreateTime(LocalDateTime.now());
-
- statDeviceDailyMapper.insert(stat);
- count++;
- }
-
- log.info("设备统计汇总完成,共{}条记录", count);
- }
- private void aggregateShopDaily(LocalDate statDate) {
- LocalDateTime startDateTime = statDate.atStartOfDay();
- LocalDateTime endDateTime = statDate.atTime(LocalTime.MAX);
-
- List<Order> orders = orderMapper.selectList(
- new LambdaQueryWrapper<Order>()
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .ge(Order::getPayTime, startDateTime)
- .le(Order::getPayTime, endDateTime)
- );
-
- Map<Long, List<Order>> shopOrderMap = orders.stream()
- .filter(o -> o.getShopId() != null)
- .collect(Collectors.groupingBy(Order::getShopId));
-
- List<Shop> shops = shopMapper.selectList(null);
- List<Device> devices = deviceMapper.selectList(null);
-
- Map<Long, Long> shopDeviceCount = devices.stream()
- .filter(d -> d.getShopId() != null)
- .collect(Collectors.groupingBy(Device::getShopId, Collectors.counting()));
-
- Set<Long> allUserIds = orders.stream()
- .map(Order::getUserId)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
-
- Map<Long, LocalDate> userFirstOrderDate = new HashMap<>();
- for (Long userId : allUserIds) {
- List<Order> userOrders = orderMapper.selectList(
- new LambdaQueryWrapper<Order>()
- .eq(Order::getUserId, userId)
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .orderByAsc(Order::getPayTime)
- .last("LIMIT 1")
- );
- if (!userOrders.isEmpty() && userOrders.get(0).getPayTime() != null) {
- userFirstOrderDate.put(userId, userOrders.get(0).getPayTime().toLocalDate());
- }
- }
-
- int count = 0;
- for (Shop shop : shops) {
- StatShopDaily stat = new StatShopDaily();
- stat.setStatDate(statDate);
- stat.setShopId(shop.getId());
- stat.setShopName(shop.getName());
- stat.setProvince(shop.getProvince());
- stat.setCity(shop.getCity());
- stat.setDistrict(shop.getDistrict());
- stat.setDeviceCount(shopDeviceCount.getOrDefault(shop.getId(), 0L).intValue());
-
- List<Order> shopOrders = shopOrderMap.getOrDefault(shop.getId(), Collections.emptyList());
- stat.setOrderCount(shopOrders.size());
-
- Set<Long> shopUserIds = shopOrders.stream()
- .map(Order::getUserId)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
- stat.setUserCount(shopUserIds.size());
-
- int newUserCount = 0;
- for (Long userId : shopUserIds) {
- LocalDate firstDate = userFirstOrderDate.get(userId);
- if (firstDate != null && firstDate.equals(statDate)) {
- newUserCount++;
- }
- }
- stat.setNewUserCount(newUserCount);
-
- BigDecimal salesAmount = shopOrders.stream()
- .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setSalesAmount(salesAmount);
- stat.setCostAmount(BigDecimal.ZERO);
- stat.setProfitAmount(salesAmount);
-
- BigDecimal profitRate = salesAmount.compareTo(BigDecimal.ZERO) > 0
- ? BigDecimal.valueOf(100)
- : BigDecimal.ZERO;
- stat.setProfitRate(profitRate);
- stat.setCreateTime(LocalDateTime.now());
-
- statShopDailyMapper.insert(stat);
- count++;
- }
-
- log.info("门店统计汇总完成,共{}条记录", count);
- }
- private void aggregateUserRepurchase(LocalDate statDate) {
- LocalDateTime startDateTime = statDate.atStartOfDay();
- LocalDateTime endDateTime = statDate.atTime(LocalTime.MAX);
-
- List<Order> todayOrders = orderMapper.selectList(
- new LambdaQueryWrapper<Order>()
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .ge(Order::getPayTime, startDateTime)
- .le(Order::getPayTime, endDateTime)
- );
-
- Map<Long, List<Order>> userOrderMap = todayOrders.stream()
- .filter(o -> o.getUserId() != null)
- .collect(Collectors.groupingBy(Order::getUserId));
-
- int count = 0;
- for (Map.Entry<Long, List<Order>> entry : userOrderMap.entrySet()) {
- Long userId = entry.getKey();
- List<Order> todayUserOrders = entry.getValue();
-
- List<Order> allUserOrders = orderMapper.selectList(
- new LambdaQueryWrapper<Order>()
- .eq(Order::getUserId, userId)
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .orderByAsc(Order::getPayTime)
- );
-
- if (allUserOrders.isEmpty()) continue;
-
- StatUserRepurchase stat = new StatUserRepurchase();
- stat.setStatDate(statDate);
- stat.setUserId(userId);
- stat.setShopId(null);
- stat.setOrderCount(todayUserOrders.size());
- stat.setTotalOrderCount(allUserOrders.size());
-
- Order firstOrder = allUserOrders.get(0);
- Order lastOrder = allUserOrders.get(allUserOrders.size() - 1);
- stat.setFirstOrderDate(firstOrder.getPayTime() != null ? firstOrder.getPayTime().toLocalDate() : null);
- stat.setLastOrderDate(lastOrder.getPayTime() != null ? lastOrder.getPayTime().toLocalDate() : null);
-
- BigDecimal totalAmount = allUserOrders.stream()
- .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- stat.setTotalAmount(totalAmount);
-
- BigDecimal avgOrderAmount = allUserOrders.size() > 0
- ? totalAmount.divide(BigDecimal.valueOf(allUserOrders.size()), 2, RoundingMode.HALF_UP)
- : BigDecimal.ZERO;
- stat.setAvgOrderAmount(avgOrderAmount);
-
- if (allUserOrders.size() >= 2) {
- long totalDays = 0;
- for (int i = 1; i < allUserOrders.size(); i++) {
- if (allUserOrders.get(i-1).getPayTime() != null && allUserOrders.get(i).getPayTime() != null) {
- long days = java.time.temporal.ChronoUnit.DAYS.between(
- allUserOrders.get(i-1).getPayTime().toLocalDate(),
- allUserOrders.get(i).getPayTime().toLocalDate()
- );
- totalDays += days;
- }
- }
- stat.setRepurchaseDays((int) (totalDays / (allUserOrders.size() - 1)));
- } else {
- stat.setRepurchaseDays(0);
- }
-
- stat.setCreateTime(LocalDateTime.now());
-
- statUserRepurchaseMapper.insert(stat);
- count++;
- }
-
- log.info("用户复购统计汇总完成,共{}条记录", count);
- }
- }
|