mitao
2025-03-14 392b42c4891cf2e6beda57ab32c51598f290f4b7
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/MgtBusinessDataService.java
@@ -1,44 +1,175 @@
package com.ruoyi.order.service.impl;
import com.alibaba.excel.EasyExcel;
import com.google.common.collect.Lists;
import com.ruoyi.common.core.enums.AuctionOrderTypeEnum;
import com.ruoyi.common.core.enums.OrderFromEnum;
import com.ruoyi.common.core.enums.OrderTypeEnum;
import com.ruoyi.common.core.enums.TimeTypeEnum;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.page.CollUtils;
import com.ruoyi.order.controller.management.dto.MgtOrderStaticsQuery;
import com.ruoyi.order.controller.management.vo.MgtAmountChartVO;
import com.ruoyi.order.controller.management.vo.MgtCountChartVO;
import com.ruoyi.order.controller.management.vo.MgtOrderStaticsChartVO;
import com.ruoyi.order.controller.management.vo.MgtOrderStaticsVO;
import com.ruoyi.order.service.IOrderService;
import com.ruoyi.system.api.domain.Order;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
/**
 * @author mitao
 * @date 2024/6/19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MgtBusinessDataService {
    private final IOrderService orderService;
    /**
     * 获取业务数据统计
     *
     * @param query 订单统计查询对象
     * @return MgtOrderStaticsVO
     */
    public MgtOrderStaticsVO getOverview(MgtOrderStaticsQuery query) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startTime = null;
        LocalDateTime endTime = null;
        getTimeByTimeType(query, startTime, endTime, now);
        return null;
        List<LocalDateTime> timeByTimeType = getTimeByTimeType(query);
        LocalDateTime startTime = timeByTimeType.get(0);
        LocalDateTime endTime = timeByTimeType.get(1);
        List<Order> orderList = orderService.getStaticsOrderList(startTime, endTime);
        MgtOrderStaticsVO mgtOrderStaticsVO = new MgtOrderStaticsVO();
        if (CollUtils.isNotEmpty(orderList)) {
            /* ***************************************商城订单统计*************************************** */
            // 商城订单统计-订单总数
            long mallOrderTotalCount = orderList.stream()
                    .filter(order -> !order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .count();
            // 商城订单统计-商城订单
            long mallOrderCount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.COMMODITY_ORDER))
                    .count();
            // 商城订单统计-秒杀订单
            long seckillOrderCount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.SNAP_ORDERS))
                    .count();
            // 商城订单统计-团购订单
            long groupPurchaseOrderCount = orderList.stream()
                    .filter(order -> order.getOrderFrom()
                            .equals(OrderFromEnum.GROUP_PURCHASE_ORDERS))
                    .count();
            // 商城订单统计-订单总金额
            BigDecimal mallOrderTotalAmount = orderList.stream()
                    .filter(order -> !order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .map(Order::getTotalAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            // 商城订单统计 - 商城总金额
            BigDecimal mallTotalAmount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.COMMODITY_ORDER))
                    .map(Order::getTotalAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            // 商城订单统计-秒杀总金额
            BigDecimal seckillTotalAmount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.SNAP_ORDERS))
                    .map(Order::getTotalAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            // 商城订单统计-团购总金额
            BigDecimal groupPurchaseTotalAmount = orderList.stream()
                    .filter(order -> order.getOrderFrom()
                            .equals(OrderFromEnum.GROUP_PURCHASE_ORDERS))
                    .map(Order::getTotalAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            mgtOrderStaticsVO.setMallOrderTotalCount(mallOrderTotalCount);
            mgtOrderStaticsVO.setMallOrderCount(mallOrderCount);
            mgtOrderStaticsVO.setSeckillOrderCount(seckillOrderCount);
            mgtOrderStaticsVO.setGroupPurchaseOrderCount(groupPurchaseOrderCount);
            mgtOrderStaticsVO.setMallOrderTotalAmount(mallOrderTotalAmount);
            mgtOrderStaticsVO.setMallTotalAmount(mallTotalAmount);
            mgtOrderStaticsVO.setSeckillTotalAmount(seckillTotalAmount);
            mgtOrderStaticsVO.setGroupPurchaseTotalAmount(groupPurchaseTotalAmount);
            /* ***************************************拍卖订单统计*************************************** */
            // 拍卖订单统计-订单总数
            long auctionOrderTotalCount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .count();
            // 拍卖订单统计-拍卖商品订单
            long auctionGoodsOrderCount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS)
                            && order.getAuctionOrderType()
                            .equals(AuctionOrderTypeEnum.REGULAR_ITEMS))
                    .count();
            // 拍卖订单统计-拍卖场订单
            long auctionSalesroomOrderCount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS)
                            && order.getAuctionOrderType()
                            .equals(AuctionOrderTypeEnum.AUCTION_ITEMS))
                    .count();
            // 拍卖订单统计-订单总金额
            BigDecimal auctionOrderTotalAmount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .map(Order::getTotalAmount)
                    .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            // 拍卖订单统计-拍卖商品总金额
            BigDecimal auctionGoodsTotalAmount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS)
                            && order.getAuctionOrderType()
                            .equals(AuctionOrderTypeEnum.REGULAR_ITEMS)
                    )
                    .map(Order::getTotalAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            // 拍卖订单统计-拍卖场总金额
            BigDecimal auctionSalesroomTotalAmount = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS)
                            && order.getAuctionOrderType()
                            .equals(AuctionOrderTypeEnum.AUCTION_ITEMS))
                    .map(Order::getTotalAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            mgtOrderStaticsVO.setAuctionOrderTotalCount(auctionOrderTotalCount);
            mgtOrderStaticsVO.setAuctionGoodsOrderCount(auctionGoodsOrderCount);
            mgtOrderStaticsVO.setAuctionSalesroomOrderCount(auctionSalesroomOrderCount);
            mgtOrderStaticsVO.setAuctionOrderTotalAmount(auctionOrderTotalAmount);
            mgtOrderStaticsVO.setAuctionGoodsTotalAmount(auctionGoodsTotalAmount);
            mgtOrderStaticsVO.setAuctionSalesroomTotalAmount(auctionSalesroomTotalAmount);
        }
        return mgtOrderStaticsVO;
    }
    private static void getTimeByTimeType(MgtOrderStaticsQuery query, LocalDateTime startTime,
            LocalDateTime endTime, LocalDateTime now) {
    private List<LocalDateTime> getTimeByTimeType(MgtOrderStaticsQuery query) {
        LocalDateTime startTime;
        LocalDateTime endTime;
        List<LocalDateTime> range = new ArrayList<>();
        LocalDateTime now = LocalDateTime.now();
        if (Objects.equals(TimeTypeEnum.TODAY.getCode(), query.getTimeType())) {
            startTime = DateUtils.getDayStart(now);
            endTime = DateUtils.getDayEnd(now);
        } else if (Objects.equals(TimeTypeEnum.PAST_SEVEN_DAYS.getCode(), query.getTimeType())) {
            startTime = DateUtils.getDayStart(now.minusDays(7));
            startTime = DateUtils.getDayStart(now.minusDays(6)); // 过去七天 为过去六天加今天
            endTime = DateUtils.getDayEnd(now);
        } else if (Objects.equals(TimeTypeEnum.PAST_THIRTY_DAYS.getCode(), query.getTimeType())) {
            startTime = DateUtils.getDayStart(now.minusDays(30));
            startTime = DateUtils.getDayStart(now.minusDays(29)); // 过去30天 为过去29天加今天
            endTime = DateUtils.getDayEnd(now);
        } else if (Objects.equals(TimeTypeEnum.CUSTOM.getCode(), query.getTimeType())) {
            if (Objects.isNull(query.getStartTime()) || Objects.isNull(query.getEndTime())) {
@@ -50,5 +181,167 @@
            startTime = DateUtils.getDayStart(now);
            endTime = DateUtils.getDayEnd(now);
        }
        range.add(startTime);
        range.add(endTime);
        return range;
    }
    /**
     * 获取图表数据
     *
     * @return MgtOrderStaticsChartVO
     */
    public MgtOrderStaticsChartVO getChart() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startTime = DateUtils.getDayStart(now.minusDays(29));
        LocalDateTime endTime = DateUtils.getDayEnd(now);
        List<LocalDate> datesBetween = getDatesBetween(startTime.toLocalDate(),
                endTime.toLocalDate());
        // 根据开始时间和结束时间,获取这个范围的每一天的日期
        List<Order> orderList = orderService.getStaticsOrderList(startTime, endTime);
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        // 商城订单总数
        CompletableFuture<List<MgtCountChartVO>> mallOrderCountFuture = CompletableFuture.supplyAsync(
                () -> getMgtCountChartVOS(datesBetween, orderList, OrderTypeEnum.MALL_ODER),
                executorService);
        // 商城订单总金额
        CompletableFuture<List<MgtAmountChartVO>> mallOrderTotalAmountFuture = CompletableFuture.supplyAsync(
                () -> getMgtAmountChartVOS(datesBetween, orderList, OrderTypeEnum.MALL_ODER),
                executorService);
        // 拍卖订单总数
        CompletableFuture<List<MgtCountChartVO>> auctionOrderCountFuture = CompletableFuture.supplyAsync(
                () -> getMgtCountChartVOS(datesBetween, orderList, OrderTypeEnum.AUCTION_ORDER),
                executorService);
        // 拍卖订单总金额
        CompletableFuture<List<MgtAmountChartVO>> auctionOrderTotalAmountFuture = CompletableFuture.supplyAsync(
                () -> getMgtAmountChartVOS(datesBetween, orderList, OrderTypeEnum.AUCTION_ORDER),
                executorService);
        // 使用 join 方法阻塞当前线程,直到所有任务完成
        CompletableFuture.allOf(mallOrderCountFuture,
                        mallOrderTotalAmountFuture, auctionOrderCountFuture, auctionOrderTotalAmountFuture)
                .join();
        MgtOrderStaticsChartVO mgtOrderStaticsChartVO = new MgtOrderStaticsChartVO();
        try {
            // 设置结果
            mgtOrderStaticsChartVO.setMallOrderCountList(mallOrderCountFuture.get());
            mgtOrderStaticsChartVO.setMallOrderTotalAmountList(mallOrderTotalAmountFuture.get());
            mgtOrderStaticsChartVO.setAuctionOrderCountList(auctionOrderCountFuture.get());
            mgtOrderStaticsChartVO.setAuctionOrderTotalAmountList(
                    auctionOrderTotalAmountFuture.get());
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        } finally {
            executorService.shutdown(); // 确保关闭线程池
        }
        return mgtOrderStaticsChartVO;
    }
    @NotNull
    private static List<MgtAmountChartVO> getMgtAmountChartVOS(List<LocalDate> datesBetween,
            List<Order> orderList, OrderTypeEnum type) {
        Map<String, BigDecimal> orderTotalAmountMap;
        if (type.equals(OrderTypeEnum.MALL_ODER)) {
            orderTotalAmountMap = orderList.stream()
                    .filter(order -> !order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .collect(Collectors.groupingBy(order -> order.getOrderTime().toLocalDate()
                                    .format(DateTimeFormatter.ofPattern("MM-dd")),
                            Collectors.reducing(BigDecimal.ZERO, Order::getTotalAmount,
                                    BigDecimal::add)));
        } else if (type.equals(OrderTypeEnum.AUCTION_ORDER)) {
            orderTotalAmountMap = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .collect(Collectors.groupingBy(order -> order.getOrderTime().toLocalDate()
                                    .format(DateTimeFormatter.ofPattern("MM-dd")),
                            Collectors.reducing(BigDecimal.ZERO, Order::getTotalAmount,
                                    BigDecimal::add)));
        } else {
            orderTotalAmountMap = null;
        }
        if (orderTotalAmountMap != null) {
            return datesBetween.stream().map(date -> {
                MgtAmountChartVO mgtAmountChartVO = new MgtAmountChartVO();
                mgtAmountChartVO.setDate(date.format(DateTimeFormatter.ofPattern("MM-dd")));
                BigDecimal orderTotalAmount = orderTotalAmountMap.get(
                        date.format(DateTimeFormatter.ofPattern("MM-dd")));
                mgtAmountChartVO.setValue(
                        orderTotalAmount == null ? BigDecimal.ZERO : orderTotalAmount);
                return mgtAmountChartVO;
            }).collect(Collectors.toList());
        } else {
            return CollUtils.emptyList();
        }
    }
    @NotNull
    private static List<MgtCountChartVO> getMgtCountChartVOS(List<LocalDate> datesBetween,
            List<Order> orderList, OrderTypeEnum type) {
        Map<String, Long> orderCountMap;
        if (type.equals(OrderTypeEnum.MALL_ODER)) {
            orderCountMap = orderList.stream()
                    .filter(order -> !order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .collect(Collectors.groupingBy(order -> order.getOrderTime().toLocalDate()
                                    .format(DateTimeFormatter.ofPattern("MM-dd")),
                            Collectors.counting()));
        } else if (type.equals(OrderTypeEnum.AUCTION_ORDER)) {
            orderCountMap = orderList.stream()
                    .filter(order -> order.getOrderFrom().equals(OrderFromEnum.AUCTION_ORDERS))
                    .collect(Collectors.groupingBy(order -> order.getOrderTime().toLocalDate()
                                    .format(DateTimeFormatter.ofPattern("MM-dd")),
                            Collectors.counting()));
        } else {
            orderCountMap = null;
        }
        if (orderCountMap != null) {
            return datesBetween.stream().map(date -> {
                MgtCountChartVO mgtCountChartVO = new MgtCountChartVO();
                mgtCountChartVO.setDate(date.format(DateTimeFormatter.ofPattern("MM-dd")));
                Long orderCount = orderCountMap.get(
                        date.format(DateTimeFormatter.ofPattern("MM-dd")));
                mgtCountChartVO.setValue(orderCount == null ? 0L : orderCount);
                return mgtCountChartVO;
            }).collect(Collectors.toList());
        } else {
            return CollUtils.emptyList();
        }
    }
    public List<LocalDate> getDatesBetween(LocalDate startDate, LocalDate endDate) {
        // 检查开始日期是否晚于结束日期
        if (startDate.isAfter(endDate)) {
            throw new IllegalArgumentException("开始日期必须早于结束日期");
        }
        // 使用ChronoUnit.DAYS.between计算日期差,并生成日期范围的流
        long numOfDaysBetween = ChronoUnit.DAYS.between(startDate, endDate);
        // 使用Stream.iterate生成日期序列,再用limit限制天数,最后collect收集结果
        return Stream.iterate(startDate, date -> date.plusDays(1))
                .limit(numOfDaysBetween + 1) // 加1是因为包含结束日期
                .collect(Collectors.toList());
    }
    /**
     * 业务数据导出
     *
     * @param query
     * @param response
     */
    public void exportBusinessData(MgtOrderStaticsQuery query, HttpServletResponse response) {
        MgtOrderStaticsVO overview = getOverview(query);
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        response.setContentType(
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        try {
            String fileName = URLEncoder.encode("业务数据", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition",
                    "attachment;filename*=utf-8''" + fileName + ".xlsx");
            EasyExcel.write(response.getOutputStream(), MgtOrderStaticsVO.class)
                    .sheet("业务数据")
                    .doWrite(Lists.newArrayList(overview));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}