diff --git a/aircraft-system/src/main/java/com/aircraft/modules/aircraft/mapper/AircraftDeviceMapper.java b/aircraft-system/src/main/java/com/aircraft/modules/aircraft/mapper/AircraftDeviceMapper.java index a66cb78..d7afd47 100644 --- a/aircraft-system/src/main/java/com/aircraft/modules/aircraft/mapper/AircraftDeviceMapper.java +++ b/aircraft-system/src/main/java/com/aircraft/modules/aircraft/mapper/AircraftDeviceMapper.java @@ -3,6 +3,9 @@ package com.aircraft.modules.aircraft.mapper; import com.aircraft.modules.aircraft.domain.AircraftDevice; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; +import java.util.Map; + /** *

* 飞行器设备表 Mapper 接口 @@ -13,4 +16,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; */ public interface AircraftDeviceMapper extends BaseMapper { + Map getAircraftDeviceById(List deviceIds); } diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/controller/OrderAnalysisController.java b/aircraft-system/src/main/java/com/aircraft/modules/order/controller/OrderAnalysisController.java new file mode 100644 index 0000000..2b825ed --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/controller/OrderAnalysisController.java @@ -0,0 +1,184 @@ +package com.aircraft.modules.order.controller; + +import com.aircraft.modules.order.domain.dto.*; +import com.aircraft.modules.order.service.OrderAnalysisService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.regex.Pattern; + +@Api(tags ="系统:订单分析") +@RestController +@RequestMapping("/fmsOdOrderAnalysis") +//每个接口都返回时间范围内的总订单量、订单总金额、进行中的订单量、完成的订单量、完成的订单率、 +// 时间范围内的所有订单信息(分页管理)、无人机型号订单占比、不同区域的订单总量 +public class OrderAnalysisController { + + private static final Logger LOG = LoggerFactory.getLogger(OrderAnalysisController.class); + @Autowired + private OrderAnalysisService orderAnalysisService; + + // 日期格式化器 + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); + private static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy"); + // 季度格式校验正则(yyyy-Q1~Q4) + private static final Pattern QUARTER_PATTERN = Pattern.compile("^\\d{4}-Q[1-4]$"); + + @ApiOperation("按日范围分析订单") + @GetMapping("/day") + //额外返回传入时间范围内的每日订单总量 + public ResponseEntity>> analyzeByDayRange( + @ApiParam(value = "开始日期(yyyy-MM-dd)", required = true, example = "2025-01-01") + @RequestParam("startDate") String startDate, + @ApiParam(value = "结束日期(yyyy-MM-dd)", required = true, example = "2025-01-31") + @RequestParam("endDate") String endDate) { + try { + // 解析日期 + LocalDate start = LocalDate.parse(startDate, DATE_FORMATTER); + LocalDate end = LocalDate.parse(endDate, DATE_FORMATTER); + + // 校验时间范围 + if (start.isAfter(end)) { + LOG.warn("按日分析:开始日期[{}]晚于结束日期[{}]", startDate, endDate); + return ResponseEntity.badRequest().body(null); + } + + // 调用服务层 + OrderAnalysisResult> result = orderAnalysisService.analyzeByDayRange(start, end); + LOG.info("按日分析完成,时间范围:{}至{}", startDate, endDate); + return ResponseEntity.ok(result); + } catch (Exception e) { + LOG.error("按日分析失败,参数:startDate={}, endDate={}", startDate, endDate, e); + return ResponseEntity.badRequest().body(null); + } + } + + @ApiOperation("按月范围分析订单") + @GetMapping("/month") + //传入的时间范围:某年的某月到某年的某月,额外返回时间范围内的每月总订单量 + public ResponseEntity>> analyzeByMonthRange( + @ApiParam(value = "开始月份(yyyy-MM)", required = true, example = "2025-01") + @RequestParam("startDate") String startDate, + @ApiParam(value = "结束月份(yyyy-MM)", required = true, example = "2025-03") + @RequestParam("endDate") String endDate) { + try { + // 解析月份为当月第一天 + LocalDate start = LocalDate.parse(startDate + "-01", DATE_FORMATTER); + // 解析结束月份为当月最后一天(自动处理大小月和闰年) + LocalDate end = LocalDate.parse(endDate + "-01", DATE_FORMATTER) + .withDayOfMonth(LocalDate.parse(endDate + "-01", DATE_FORMATTER).lengthOfMonth()); + + // 校验时间范围 + if (start.isAfter(end)) { + LOG.warn("按月分析:开始月份[{}]晚于结束月份[{}]", startDate, endDate); + return ResponseEntity.badRequest().body(null); + } + + // 调用服务层 + OrderAnalysisResult> result = orderAnalysisService.analyzeByMonthRange(start, end); + LOG.info("按月分析完成,时间范围:{}至{}", startDate, endDate); + return ResponseEntity.ok(result); + } catch (Exception e) { + LOG.error("按月分析失败,参数:startDate={}, endDate={}", startDate, endDate, e); + return ResponseEntity.badRequest().body(null); + } + } + + @ApiOperation("按季度范围分析订单") + @GetMapping("/quarter") + //传入的时间范围:某年的某季到某年的某季,yyyy-Qn (例: 2025-Q1),额外返回时间范围内的每季总订单量 + public ResponseEntity>> analyzeByQuarterRange( + @ApiParam(value = "开始季度(yyyy-Qn,例:2025-Q1)", required = true) + @RequestParam("startDate") String startDate, + @ApiParam(value = "结束季度(yyyy-Qn,例:2025-Q4)", required = true) + @RequestParam("endDate") String endDate) { + try { + // 校验季度格式 + if (!QUARTER_PATTERN.matcher(startDate).matches() || !QUARTER_PATTERN.matcher(endDate).matches()) { + LOG.warn("按季度分析:格式错误,startDate={}, endDate={}(正确格式:yyyy-Q1~Q4)", startDate, endDate); + return ResponseEntity.badRequest().body(null); + } + + // 解析季度(格式:yyyy-Qn → 年份和季度数) + String[] startParts = startDate.split("-Q"); + int startYear = Integer.parseInt(startParts[0]); + int startQuarter = Integer.parseInt(startParts[1]); + + String[] endParts = endDate.split("-Q"); + int endYear = Integer.parseInt(endParts[0]); + int endQuarter = Integer.parseInt(endParts[1]); + + // 计算季度对应的日期范围(季度第一天至季度最后一天) + LocalDate start = getQuarterFirstDay(startYear, startQuarter); + LocalDate end = getQuarterLastDay(endYear, endQuarter); + + // 校验时间范围 + if (start.isAfter(end)) { + LOG.warn("按季度分析:开始季度[{}]晚于结束季度[{}]", startDate, endDate); + return ResponseEntity.badRequest().body(null); + } + + // 调用服务层 + OrderAnalysisResult> result = orderAnalysisService.analyzeByQuarterRange(start, end); + LOG.info("按季度分析完成,时间范围:{}至{}", startDate, endDate); + return ResponseEntity.ok(result); + } catch (Exception e) { + LOG.error("按季度分析失败,参数:startDate={}, endDate={}", startDate, endDate, e); + return ResponseEntity.badRequest().body(null); + } + } + + + @ApiOperation("按年范围分析订单") + @GetMapping("/year") + //传入的时间范围;某年到某年,额外返回时间范围内的每年总订单量 + public ResponseEntity>> analyzeByYearRange( + @ApiParam(value = "开始年份(yyyy)", required = true, example = "2023") + @RequestParam("startDate") String startDate, + @ApiParam(value = "结束年份(yyyy)", required = true, example = "2025") + @RequestParam("endDate") String endDate) { + try { + // 解析年份为当年第一天和最后一天 + LocalDate start = LocalDate.parse(startDate + "-01-01", DATE_FORMATTER); + LocalDate end = LocalDate.parse(endDate + "-12-31", DATE_FORMATTER); + + // 校验时间范围 + if (start.isAfter(end)) { + LOG.warn("按年分析:开始年份[{}]晚于结束年份[{}]", startDate, endDate); + return ResponseEntity.badRequest().body(null); + } + + // 调用服务层 + OrderAnalysisResult> result = orderAnalysisService.analyzeByYearRange(start, end); + LOG.info("按年分析完成,时间范围:{}至{}", startDate, endDate); + return ResponseEntity.ok(result); + } catch (Exception e) { + LOG.error("按年分析失败,参数:startDate={}, endDate={}", startDate, endDate, e); + return ResponseEntity.badRequest().body(null); + } + } // 工具方法:获取季度第一天(如2025-Q1 → 2025-01-01) + + private LocalDate getQuarterFirstDay(int year, int quarter) { + int month = (quarter - 1) * 3 + 1; + return LocalDate.of(year, month, 1); + } + + // 工具方法:获取季度最后一天(如2025-Q1 → 2025-03-31) + private LocalDate getQuarterLastDay(int year, int quarter) { + int month = quarter * 3; + return LocalDate.of(year, month, 1).withDayOfMonth(LocalDate.of(year, month, 1).lengthOfMonth()); + } +} diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/OrderDetail.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/OrderDetail.java index 2d9bd77..18459f0 100644 --- a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/OrderDetail.java +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/OrderDetail.java @@ -51,7 +51,7 @@ public class OrderDetail extends BaseEntity { /** * 订单子单状态 */ - private Boolean orderItemStatus; + private Integer orderItemStatus; } diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderAnalysisResult.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderAnalysisResult.java new file mode 100644 index 0000000..2e7f028 --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderAnalysisResult.java @@ -0,0 +1,25 @@ +package com.aircraft.modules.order.domain.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +@Data +public class OrderAnalysisResult { + private Long totalOrderCount; // 总订单量 + private BigDecimal totalOrderAmount; // 订单总金额 + private Long noStartOrderCount; //未开始的订单量 + private Long inProgressOrderCount; // 进行中的订单量 + private Long completedOrderCount; // 完成的订单量 + private Double completionRate; // 完成的订单率 + private PageInfo pageInfo; // 分页信息 + private List orderList; // 订单列表 + private Map droneModelDistribution; // 无人机型号订单占比 + private Map routeOrderDistribution; // 不同区域的订单总量 + private T timeSeriesData; // 时间序列数据(日/月/年统计) +} + + + diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderDailyStat.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderDailyStat.java new file mode 100644 index 0000000..26b030f --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderDailyStat.java @@ -0,0 +1,10 @@ +package com.aircraft.modules.order.domain.dto; + +import lombok.Data; + +//每日的订单总量统计实体类 +@Data +public class OrderDailyStat { + private String date; + private Long orderCount; +} diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderMonthlyStat.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderMonthlyStat.java new file mode 100644 index 0000000..0676113 --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderMonthlyStat.java @@ -0,0 +1,9 @@ +package com.aircraft.modules.order.domain.dto; + +import lombok.Data; + +@Data +public class OrderMonthlyStat { + private String month; + private Long orderCount; +} diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderQuarterlyStat.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderQuarterlyStat.java new file mode 100644 index 0000000..f10406c --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderQuarterlyStat.java @@ -0,0 +1,9 @@ +package com.aircraft.modules.order.domain.dto; + +import lombok.Data; + +@Data +public class OrderQuarterlyStat { + private String quarter; // 季度标识(如2025-Q1) + private Long orderCount; // 本季度订单总量 +} diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderYearlyStat.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderYearlyStat.java new file mode 100644 index 0000000..fce0ee5 --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/OrderYearlyStat.java @@ -0,0 +1,9 @@ +package com.aircraft.modules.order.domain.dto; + +import lombok.Data; + +@Data +public class OrderYearlyStat { + private String year; + private Long orderCount; +} \ No newline at end of file diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/PageInfo.java b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/PageInfo.java new file mode 100644 index 0000000..e855901 --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/domain/dto/PageInfo.java @@ -0,0 +1,10 @@ +package com.aircraft.modules.order.domain.dto; + +import lombok.Data; + +@Data +public class PageInfo { + private Integer pageNum; + private Integer pageSize; + private Long total; +} diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/mapper/OrderDetailMapper.java b/aircraft-system/src/main/java/com/aircraft/modules/order/mapper/OrderDetailMapper.java index 3eb2779..02d713c 100644 --- a/aircraft-system/src/main/java/com/aircraft/modules/order/mapper/OrderDetailMapper.java +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/mapper/OrderDetailMapper.java @@ -3,6 +3,8 @@ package com.aircraft.modules.order.mapper; import com.aircraft.modules.order.domain.OrderDetail; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; + /** *

* 订单明细表 Mapper 接口 @@ -13,4 +15,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; */ public interface OrderDetailMapper extends BaseMapper { + List getOrderDetailsByOrderIds(List orderIds); + + List getOrderDetailsByOrderId(Long id); } diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/service/OrderAnalysisService.java b/aircraft-system/src/main/java/com/aircraft/modules/order/service/OrderAnalysisService.java new file mode 100644 index 0000000..e9e9d5b --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/service/OrderAnalysisService.java @@ -0,0 +1,19 @@ +package com.aircraft.modules.order.service; + + +import com.aircraft.modules.order.domain.dto.*; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; + +@Service +public interface OrderAnalysisService { + OrderAnalysisResult> analyzeByDayRange(LocalDate start, LocalDate end); + + OrderAnalysisResult> analyzeByMonthRange(LocalDate start, LocalDate end); + + OrderAnalysisResult> analyzeByQuarterRange(LocalDate start, LocalDate end); + + OrderAnalysisResult> analyzeByYearRange(LocalDate start, LocalDate end); +} diff --git a/aircraft-system/src/main/java/com/aircraft/modules/order/service/impl/OrderAnalysisServiceImpl.java b/aircraft-system/src/main/java/com/aircraft/modules/order/service/impl/OrderAnalysisServiceImpl.java new file mode 100644 index 0000000..fe01683 --- /dev/null +++ b/aircraft-system/src/main/java/com/aircraft/modules/order/service/impl/OrderAnalysisServiceImpl.java @@ -0,0 +1,479 @@ +package com.aircraft.modules.order.service.impl; + +import com.aircraft.modules.aircraft.domain.AircraftDevice; +import com.aircraft.modules.aircraft.mapper.AircraftDeviceMapper; +import com.aircraft.modules.order.domain.OrderDetail; +import com.aircraft.modules.order.domain.OrderMain; +import com.aircraft.modules.order.domain.dto.*; +import com.aircraft.modules.order.mapper.OrderDetailMapper; +import com.aircraft.modules.order.mapper.OrderMainMapper; +import com.aircraft.modules.order.service.OrderAnalysisService; +import com.aircraft.modules.route.domain.CpRoute; +import com.aircraft.modules.route.mapper.CpRouteMapper; +import com.aircraft.modules.system.service.EmAreaService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Service +public class OrderAnalysisServiceImpl implements OrderAnalysisService { + + @Autowired + private OrderMainMapper orderMainMapper; + @Autowired + private AircraftDeviceMapper aircraftDeviceMapper; + @Autowired + private OrderDetailMapper orderDetailMapper; + @Autowired + private CpRouteMapper cpRouteMapper; + + // 日期格式化器(用于按日/月/季度/年分组) + private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); + private static final DateTimeFormatter QUARTER_FORMATTER = DateTimeFormatter.ofPattern("yyyy-Qq"); // 如2025-Q1 + private static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy"); + + // 订单状态 + private static final Integer STATUS_NOTSTART = 0; + private static final Integer STATUS_IN_PROGRESS = 1; + private static final Integer STATUS_COMPLETED = 2; + + + /** + * 按日范围分析订单 + */ + @Override + public OrderAnalysisResult> analyzeByDayRange(LocalDate start, LocalDate end) { + // 1. 查询时间范围内的所有订单 + List orders = queryOrdersByDateRange(start, end); + if (CollectionUtils.isEmpty(orders)) { + return buildEmptyResult(); + } + + // 2. 按日分组统计每日订单量 + Map dailyCountMap = orders.stream() + .collect(Collectors.groupingBy( + order -> { + LocalDate localDate = order.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + return localDate.format(DAY_FORMATTER); // 按日期字符串分组 + }, + Collectors.counting() // 统计每组数量 + )); + + System.out.println(dailyCountMap); + + // 3. 填充每日统计数据(确保日期连续性,即使某天无订单也显示0) + List dailyStats = new ArrayList<>(); + LocalDate current = start; + while (!current.isAfter(end)) { + String dateStr = current.format(DAY_FORMATTER); + OrderDailyStat stat = new OrderDailyStat(); + stat.setDate(dateStr); + stat.setOrderCount(dailyCountMap.getOrDefault(dateStr, 0L)); + dailyStats.add(stat); + current = current.plusDays(1); // 移至下一天 + } + + // 4. 构建并返回完整结果 + return buildAnalysisResult(orders, dailyStats); + } + + + /** + * 按月范围分析订单 + */ + @Override + public OrderAnalysisResult> analyzeByMonthRange(LocalDate start, LocalDate end) { + // 1. 查询时间范围内的所有订单 + List orders = queryOrdersByDateRange(start, end); + if (CollectionUtils.isEmpty(orders)) { + return buildEmptyResult(); + } + + // 2. 按月分组统计每月订单量 + Map monthlyCountMap = orders.stream() + .collect(Collectors.groupingBy( + order -> { + LocalDate localDate = order.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + return localDate.format(MONTH_FORMATTER); + }, + Collectors.counting() + )); + + // 3. 填充每月统计数据(确保月份连续性) + List monthlyStats = new ArrayList<>(); + LocalDate current = start.withDayOfMonth(1); // 从当月第一天开始 + LocalDate endMonth = end.withDayOfMonth(1); // 结束月份第一天 + + while (!current.isAfter(endMonth)) { + String monthStr = current.format(MONTH_FORMATTER); + OrderMonthlyStat stat = new OrderMonthlyStat(); + stat.setMonth(monthStr); + stat.setOrderCount(monthlyCountMap.getOrDefault(monthStr, 0L)); + monthlyStats.add(stat); + current = current.plusMonths(1); // 移至下一月 + } + + // 4. 构建并返回完整结果 + return buildAnalysisResult(orders, monthlyStats); + } + + + /** + * 按季度范围分析订单 + */ + @Override + public OrderAnalysisResult> analyzeByQuarterRange(LocalDate start, LocalDate end) { + // 1. 查询时间范围内的所有订单 + List orders = queryOrdersByDateRange(start, end); + if (CollectionUtils.isEmpty(orders)) { + return buildEmptyResult(); + } + + // 2. 按季度分组统计每季度订单量 + Map quarterCountMap = orders.stream() + .collect(Collectors.groupingBy( + order -> { + // 将 java.util.Date 转换为 java.time.YearMonth + YearMonth yearMonth = YearMonth.from(order.getCreateTime().toInstant().atZone(ZoneId.systemDefault())); + int quarter = (yearMonth.getMonthValue() - 1) / 3 + 1; + return yearMonth.format(YEAR_FORMATTER) + "-Q" + quarter; + }, + Collectors.counting() + )); + + // 3. 填充每季度统计数据(确保季度连续性) + List quarterlyStats = new ArrayList<>(); + LocalDate currentQuarterStart = start; + // 调整至当前季度第一天(如2025-05-10 → 2025-04-01) + currentQuarterStart = currentQuarterStart.minusMonths((currentQuarterStart.getMonthValue() - 1) % 3); + currentQuarterStart = currentQuarterStart.withDayOfMonth(1); + + while (!currentQuarterStart.isAfter(end)) { + // 计算当前季度标识(如2025-Q2) + int quarter = (currentQuarterStart.getMonthValue() - 1) / 3 + 1; + String quarterStr = currentQuarterStart.format(YEAR_FORMATTER) + "-Q" + quarter; + + OrderQuarterlyStat stat = new OrderQuarterlyStat(); + stat.setQuarter(quarterStr); + stat.setOrderCount(quarterCountMap.getOrDefault(quarterStr, 0L)); + quarterlyStats.add(stat); + + // 移至下一季度第一天 + currentQuarterStart = currentQuarterStart.plusMonths(3); + } + + // 4. 构建并返回完整结果 + return buildAnalysisResult(orders, quarterlyStats); + } + + + /** + * 按年范围分析订单 + */ + @Override + public OrderAnalysisResult> analyzeByYearRange(LocalDate start, LocalDate end) { + // 1. 查询时间范围内的所有订单 + List orders = queryOrdersByDateRange(start, end); + if (CollectionUtils.isEmpty(orders)) { + return buildEmptyResult(); + } + + // 2. 按年分组统计每年订单量 + Map yearCountMap = orders.stream() + .collect(Collectors.groupingBy( + order -> { + LocalDate localDate = order.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + return localDate.format(YEAR_FORMATTER); + }, + Collectors.counting() + )); + // 3. 填充每年统计数据(确保年份连续性) + List yearlyStats = new ArrayList<>(); + int startYear = start.getYear(); + int endYear = end.getYear(); + + for (int year = startYear; year <= endYear; year++) { + String yearStr = String.valueOf(year); + OrderYearlyStat stat = new OrderYearlyStat(); + stat.setYear(yearStr); + stat.setOrderCount(yearCountMap.getOrDefault(yearStr, 0L)); + yearlyStats.add(stat); + } + + // 4. 构建并返回完整结果 + return buildAnalysisResult(orders, yearlyStats); + } + + +// ====================== 通用工具方法 ====================== + + /** + * 根据日期范围查询订单 + */ + private List queryOrdersByDateRange(LocalDate start, LocalDate end) { + // MyBatis Plus 条件构造器:查询 create_time 在 [start, end] 范围内的订单 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ge(OrderMain::getCreateTime, start.atStartOfDay()) // 开始时间:当天00:00:00 + .le(OrderMain::getCreateTime, end.atTime(23, 59, 59)); // 结束时间:当天23:59:59 + return orderMainMapper.selectList(queryWrapper); + } + + /** + * 构建完整的订单分析结果 + */ + private OrderAnalysisResult buildAnalysisResult(List orders, T timeSeriesData) { + OrderAnalysisResult result = new OrderAnalysisResult<>(); + + // 1. 基础统计指标 + result.setTotalOrderCount((long) orders.size()); // 总订单量 + result.setTotalOrderAmount(calculateTotalAmount(orders)); // 总金额 + + // 2. 状态统计 + long noStartCount=countOrdersByStatus(orders,STATUS_NOTSTART); + long inProgressCount = countOrdersByStatus(orders, STATUS_IN_PROGRESS); + long completedCount = countOrdersByStatus(orders, STATUS_COMPLETED); + result.setNoStartOrderCount(noStartCount); + result.setInProgressOrderCount(inProgressCount); + result.setCompletedOrderCount(completedCount); + // 计算完成率(避免除0) + result.setCompletionRate(orders.isEmpty() ? 0D : (completedCount * 100.0) / orders.size()); + + // 3. 分页信息和订单列表(默认取第一页,每页10条,实际可根据前端参数调整) + Page page = new Page<>(1, 10); + result.setPageInfo(buildPageInfo(page, orders.size())); + // 截取分页数据(实际应在SQL层分页,此处简化) + List pageOrders = orders.stream() + .skip((page.getCurrent() - 1) * page.getSize()) + .limit(page.getSize()) + .collect(Collectors.toList()); + result.setOrderList(pageOrders); + + // 4. 无人机型号占比 + result.setDroneModelDistribution(calculateDroneModelDistribution(orders)); + + // 5. 景区路线订单分布 + result.setRouteOrderDistribution(calculateRouteDistribution(orders)); + + // 6. 时间序列数据(日/月/季/年统计) + result.setTimeSeriesData(timeSeriesData); + + return result; + } + + /** + * 计算订单总金额 + */ + private BigDecimal calculateTotalAmount(List orders) { + return orders.stream() + .map(OrderMain::getAmount) // 假设订单实体有 amount 字段 + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + /** + * 按状态统计订单数量 + */ + private long countOrdersByStatus(List orders,Integer orderItemStatus) { + if (orders == null) return 0; + + // 遍历订单主表,统计包含指定状态子项的订单数量 + return orders.stream() + .filter(order -> { + // 获取当前订单的所有子项 + List orderDetails = orderDetailMapper.getOrderDetailsByOrderId(order.getId()); + + // 如果订单没有子项,不统计 + if (orderDetails == null || orderDetails.isEmpty()) { + return false; + } + + // 如果未指定状态,只要有子项就统计 + if (orderItemStatus == null) { + return true; + } + + // 检查是否有子项符合指定状态 + return orderDetails.stream() + .anyMatch(item -> orderItemStatus.equals(item.getOrderItemStatus())); + }) + .count(); + } + + /** + * 计算无人机型号订单占比 + */ + private Map calculateDroneModelDistribution(List orders) { + if (orders == null || orders.isEmpty()) { + return Collections.emptyMap(); + } + + // 根据订单id,找到对应的订单详情(OrderDetail)数据 + List orderDetails = fetchOrderDetailsByOrderIds( + orders.stream() + .map(OrderMain::getId) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + ); + + if (orderDetails.isEmpty()) { + return Collections.emptyMap(); + } + + // 从订单详情中提取所有飞行设备 ID(去重处理) + List deviceIds = orderDetails.stream() + .map(OrderDetail::getDeviceId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + // 获取飞行设备 ID 对应的 AircraftDevice 对象的映射 + Map deviceMap = aircraftDeviceMapper.getAircraftDeviceById(deviceIds); + + // 按型号分组统计数量(处理设备不存在的情况) + Map modelCountMap = orderDetails.stream() + .filter(detail -> detail.getDeviceId() != null && deviceMap != null && deviceMap.containsKey(detail.getDeviceId())) + .map(detail -> deviceMap.get(detail.getDeviceId()).getModel()) + .filter(Objects::nonNull) + .collect(Collectors.groupingBy(model -> model, Collectors.counting())); + + // 转换为占比(百分比),基于有效订单详情数量计算 + Map distribution = new HashMap<>(); + long validDetailCount = modelCountMap.values().stream().mapToLong(Long::longValue).sum(); + + if (validDetailCount > 0) { + modelCountMap.forEach((model, count) -> { + double percentage = (count * 100.0) / validDetailCount; + distribution.put(model, Math.round(percentage * 100.0) / 100.0); // 保留两位小数 + }); + } + + return distribution; + } + + // 通过订单ID获取订单详情的方法 + @Transactional(readOnly = true) + public List fetchOrderDetailsByOrderIds(List orderIds) { + // 参数校验 + if (orderIds == null || orderIds.isEmpty()) { + throw new IllegalArgumentException("订单ID列表不能为空"); + } + + // 分批查询(每批1000个ID,防止SQL IN子句过长) + final int BATCH_SIZE = 1000; + List result = new ArrayList<>(); + + for (List batchIds : partitionList(orderIds, BATCH_SIZE)) { + List batchResult = orderDetailMapper.getOrderDetailsByOrderIds(batchIds); + if (batchResult != null) { + result.addAll(batchResult); + } + } + + return result; + } + + /** + * 将列表按指定大小分割为多个子列表 + * + * @param list 原始列表 + * @param batchSize 每个子列表的最大大小 + * @return 子列表集合 + */ + private List> partitionList(List list, int batchSize) { + if (list == null || list.isEmpty()) { + return Collections.emptyList(); + } + + return IntStream.range(0, (list.size() + batchSize - 1) / batchSize) + .mapToObj(i -> list.subList(i * batchSize, Math.min((i + 1) * batchSize, list.size()))) + .collect(Collectors.toList()); + } + + /** + * 计算景区内各路线的订单分布 + * + * @param orders 订单列表 + * @return 路线订单分布映射,键为路线名称,值为对应订单数量 + */ + private Map calculateRouteDistribution(List orders) { + Map routeDistribution = new HashMap<>(); + + // 遍历所有订单,统计各路线的订单数量 + for (OrderMain order : orders) { + Long routeId = order.getRouteId(); + String routeName = cpRouteMapper.getNameById(routeId); + if (routeName != null) { + routeDistribution.put(routeName, routeDistribution.getOrDefault(routeName, 0L) + 1); + } + } + return routeDistribution; + } + /** + * 计算区域订单分布 + */ +// private Map calculateRegionDistribution(List orders) { +// if (orders == null || orders.isEmpty()) { +// return Collections.emptyMap(); +// } +// +// // 提取所有订单中的区域ID(去重) +// List regionIds = orders.stream() +// .map(OrderMain::getAttractionId) // 获取区域id +// .filter(Objects::nonNull) +// .distinct() +// .collect(Collectors.toList()); +// +// // 通过区域ID查询区域名称映射 +// Map regionNameMap = emAreaService.getNameMapByIds(regionIds); +// +// // 按区域名称分组统计订单数量 +// return orders.stream() +// .filter(order -> order.getAttractionId() != null && regionNameMap.containsKey(order.getAttractionId())) +// .collect(Collectors.groupingBy( +// order -> regionNameMap.get(order.getAttractionId()), // 使用景区区域名称作为分组键 +// Collectors.counting() +// )); +// } + + /** + * 构建分页信息 + */ + private PageInfo buildPageInfo(Page page, int total) { + PageInfo pageInfo = new PageInfo(); + pageInfo.setPageNum((int) page.getCurrent()); + pageInfo.setPageSize((int) page.getSize()); + pageInfo.setTotal((long) total); + return pageInfo; + } + + /** + * 构建空结果(无订单时) + */ + private OrderAnalysisResult buildEmptyResult() { + OrderAnalysisResult result = new OrderAnalysisResult<>(); + result.setTotalOrderCount(0L); + result.setTotalOrderAmount(BigDecimal.ZERO); + result.setInProgressOrderCount(0L); + result.setCompletedOrderCount(0L); + result.setCompletionRate(0D); + result.setPageInfo(new PageInfo()); + result.setOrderList(Collections.emptyList()); + result.setDroneModelDistribution(Collections.emptyMap()); + result.setRouteOrderDistribution(Collections.emptyMap()); + result.setTimeSeriesData(null); + return result; + } +} \ No newline at end of file diff --git a/aircraft-system/src/main/java/com/aircraft/modules/route/mapper/CpRouteMapper.java b/aircraft-system/src/main/java/com/aircraft/modules/route/mapper/CpRouteMapper.java index c2390ed..e251aca 100644 --- a/aircraft-system/src/main/java/com/aircraft/modules/route/mapper/CpRouteMapper.java +++ b/aircraft-system/src/main/java/com/aircraft/modules/route/mapper/CpRouteMapper.java @@ -18,4 +18,6 @@ public interface CpRouteMapper extends BaseMapper { void updateDelFlagById(Long id, Integer delFlag); CpRoute getRouteById(Integer id); + + String getNameById(Long routeId); } diff --git a/aircraft-system/src/main/java/com/aircraft/modules/system/service/EmAreaService.java b/aircraft-system/src/main/java/com/aircraft/modules/system/service/EmAreaService.java index 158d072..51959a3 100644 --- a/aircraft-system/src/main/java/com/aircraft/modules/system/service/EmAreaService.java +++ b/aircraft-system/src/main/java/com/aircraft/modules/system/service/EmAreaService.java @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.service.IService; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Map; /** *

@@ -53,4 +54,5 @@ public interface EmAreaService extends IService { */ List findByIdIn(List collect); + Map getNameMapByIds(List regionIds); } diff --git a/aircraft-system/src/main/java/com/aircraft/modules/system/service/impl/EmAreaServiceImpl.java b/aircraft-system/src/main/java/com/aircraft/modules/system/service/impl/EmAreaServiceImpl.java index 6e63e41..d1348d0 100644 --- a/aircraft-system/src/main/java/com/aircraft/modules/system/service/impl/EmAreaServiceImpl.java +++ b/aircraft-system/src/main/java/com/aircraft/modules/system/service/impl/EmAreaServiceImpl.java @@ -8,6 +8,7 @@ import com.aircraft.modules.system.service.EmScenicService; import com.aircraft.utils.PageResult; import com.aircraft.utils.PageUtil; import com.aircraft.utils.StringUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -15,8 +16,12 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; - +import java.util.LinkedHashMap; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; /** *

@@ -71,6 +76,31 @@ public class EmAreaServiceImpl extends ServiceImpl impleme return super.list(wrapper); } + + @Override + public Map getNameMapByIds(List regionIds) { + if (regionIds == null || regionIds.isEmpty()) { + return Collections.emptyMap(); + } + + // 使用MyBatis-Plus的条件构造器查询区域信息 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(EmArea::getId, regionIds); + + // 执行查询 + List areas = list(queryWrapper); + + // 将结果转换为Map + return areas.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap( + area -> area.getId().longValue(), // 区域ID + EmArea::getName, // 区域名称 + (existing, replacement) -> existing, // 处理重复键的策略 + LinkedHashMap::new // 使用有序Map保持插入顺序 + )); + } + /** * 构建查询 * diff --git a/aircraft-system/src/main/resources/mapper/EmAreaMapper.xml b/aircraft-system/src/main/resources/mapper/EmAreaMapper.xml index 315a0ef..d3f80e3 100644 --- a/aircraft-system/src/main/resources/mapper/EmAreaMapper.xml +++ b/aircraft-system/src/main/resources/mapper/EmAreaMapper.xml @@ -16,4 +16,22 @@ WHERE name = #{name} AND valid = 't' + + + + + + + + diff --git a/aircraft-system/src/main/resources/mapper/aircraft/AircraftDeviceMapper.xml b/aircraft-system/src/main/resources/mapper/aircraft/AircraftDeviceMapper.xml index 80a22db..e07154a 100644 --- a/aircraft-system/src/main/resources/mapper/aircraft/AircraftDeviceMapper.xml +++ b/aircraft-system/src/main/resources/mapper/aircraft/AircraftDeviceMapper.xml @@ -1,5 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aircraft-system/src/main/resources/mapper/order/OrderDetailMapper.xml b/aircraft-system/src/main/resources/mapper/order/OrderDetailMapper.xml index 482d9e4..8f72b20 100644 --- a/aircraft-system/src/main/resources/mapper/order/OrderDetailMapper.xml +++ b/aircraft-system/src/main/resources/mapper/order/OrderDetailMapper.xml @@ -1,5 +1,59 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/aircraft-system/src/main/resources/mapper/route/CpRouteMapper.xml b/aircraft-system/src/main/resources/mapper/route/CpRouteMapper.xml index 8cfe663..5e849cc 100644 --- a/aircraft-system/src/main/resources/mapper/route/CpRouteMapper.xml +++ b/aircraft-system/src/main/resources/mapper/route/CpRouteMapper.xml @@ -14,4 +14,9 @@ WHERE id = #{id} AND del_flag = 0 +