| | |
| | | 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())) { |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |