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
+