订单分析

This commit is contained in:
温文静WWW 2025-07-12 20:35:02 +08:00
parent 7d2204924e
commit ffa117c874
19 changed files with 921 additions and 2 deletions

View File

@ -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;
/**
* <p>
* 飞行器设备表 Mapper 接口
@ -13,4 +16,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface AircraftDeviceMapper extends BaseMapper<AircraftDevice> {
Map<Long, AircraftDevice> getAircraftDeviceById(List<Long> deviceIds);
}

View File

@ -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<OrderAnalysisResult<List<OrderDailyStat>>> 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<List<OrderDailyStat>> 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<OrderAnalysisResult<List<OrderMonthlyStat>>> 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<List<OrderMonthlyStat>> 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<OrderAnalysisResult<List<OrderQuarterlyStat>>> analyzeByQuarterRange(
@ApiParam(value = "开始季度yyyy-Qn2025-Q1", required = true)
@RequestParam("startDate") String startDate,
@ApiParam(value = "结束季度yyyy-Qn2025-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<List<OrderQuarterlyStat>> 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<OrderAnalysisResult<List<OrderYearlyStat>>> 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<List<OrderYearlyStat>> 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());
}
}

View File

@ -51,7 +51,7 @@ public class OrderDetail extends BaseEntity {
/**
* 订单子单状态
*/
private Boolean orderItemStatus;
private Integer orderItemStatus;
}

View File

@ -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<T> {
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<String, Double> droneModelDistribution; // 无人机型号订单占比
private Map<String, Long> routeOrderDistribution; // 不同区域的订单总量
private T timeSeriesData; // 时间序列数据//年统计
}

View File

@ -0,0 +1,10 @@
package com.aircraft.modules.order.domain.dto;
import lombok.Data;
//每日的订单总量统计实体类
@Data
public class OrderDailyStat {
private String date;
private Long orderCount;
}

View File

@ -0,0 +1,9 @@
package com.aircraft.modules.order.domain.dto;
import lombok.Data;
@Data
public class OrderMonthlyStat {
private String month;
private Long orderCount;
}

View File

@ -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; // 本季度订单总量
}

View File

@ -0,0 +1,9 @@
package com.aircraft.modules.order.domain.dto;
import lombok.Data;
@Data
public class OrderYearlyStat {
private String year;
private Long orderCount;
}

View File

@ -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;
}

View File

@ -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;
/**
* <p>
* 订单明细表 Mapper 接口
@ -13,4 +15,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
List<OrderDetail> getOrderDetailsByOrderIds(List<Long> orderIds);
List<OrderDetail> getOrderDetailsByOrderId(Long id);
}

View File

@ -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<List<OrderDailyStat>> analyzeByDayRange(LocalDate start, LocalDate end);
OrderAnalysisResult<List<OrderMonthlyStat>> analyzeByMonthRange(LocalDate start, LocalDate end);
OrderAnalysisResult<List<OrderQuarterlyStat>> analyzeByQuarterRange(LocalDate start, LocalDate end);
OrderAnalysisResult<List<OrderYearlyStat>> analyzeByYearRange(LocalDate start, LocalDate end);
}

View File

@ -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<List<OrderDailyStat>> analyzeByDayRange(LocalDate start, LocalDate end) {
// 1. 查询时间范围内的所有订单
List<OrderMain> orders = queryOrdersByDateRange(start, end);
if (CollectionUtils.isEmpty(orders)) {
return buildEmptyResult();
}
// 2. 按日分组统计每日订单量
Map<String, Long> 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<OrderDailyStat> 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<List<OrderMonthlyStat>> analyzeByMonthRange(LocalDate start, LocalDate end) {
// 1. 查询时间范围内的所有订单
List<OrderMain> orders = queryOrdersByDateRange(start, end);
if (CollectionUtils.isEmpty(orders)) {
return buildEmptyResult();
}
// 2. 按月分组统计每月订单量
Map<String, Long> 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<OrderMonthlyStat> 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<List<OrderQuarterlyStat>> analyzeByQuarterRange(LocalDate start, LocalDate end) {
// 1. 查询时间范围内的所有订单
List<OrderMain> orders = queryOrdersByDateRange(start, end);
if (CollectionUtils.isEmpty(orders)) {
return buildEmptyResult();
}
// 2. 按季度分组统计每季度订单量
Map<String, Long> 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<OrderQuarterlyStat> 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<List<OrderYearlyStat>> analyzeByYearRange(LocalDate start, LocalDate end) {
// 1. 查询时间范围内的所有订单
List<OrderMain> orders = queryOrdersByDateRange(start, end);
if (CollectionUtils.isEmpty(orders)) {
return buildEmptyResult();
}
// 2. 按年分组统计每年订单量
Map<String, Long> 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<OrderYearlyStat> 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<OrderMain> queryOrdersByDateRange(LocalDate start, LocalDate end) {
// MyBatis Plus 条件构造器查询 create_time [start, end] 范围内的订单
LambdaQueryWrapper<OrderMain> 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 <T> OrderAnalysisResult<T> buildAnalysisResult(List<OrderMain> orders, T timeSeriesData) {
OrderAnalysisResult<T> 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<OrderMain> page = new Page<>(1, 10);
result.setPageInfo(buildPageInfo(page, orders.size()));
// 截取分页数据实际应在SQL层分页此处简化
List<OrderMain> 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<OrderMain> orders) {
return orders.stream()
.map(OrderMain::getAmount) // 假设订单实体有 amount 字段
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* 按状态统计订单数量
*/
private long countOrdersByStatus(List<OrderMain> orders,Integer orderItemStatus) {
if (orders == null) return 0;
// 遍历订单主表统计包含指定状态子项的订单数量
return orders.stream()
.filter(order -> {
// 获取当前订单的所有子项
List<OrderDetail> 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<String, Double> calculateDroneModelDistribution(List<OrderMain> orders) {
if (orders == null || orders.isEmpty()) {
return Collections.emptyMap();
}
// 根据订单id找到对应的订单详情OrderDetail数据
List<OrderDetail> orderDetails = fetchOrderDetailsByOrderIds(
orders.stream()
.map(OrderMain::getId)
.filter(Objects::nonNull)
.collect(Collectors.toList())
);
if (orderDetails.isEmpty()) {
return Collections.emptyMap();
}
// 从订单详情中提取所有飞行设备 ID去重处理
List<Long> deviceIds = orderDetails.stream()
.map(OrderDetail::getDeviceId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
// 获取飞行设备 ID 对应的 AircraftDevice 对象的映射
Map<Long, AircraftDevice> deviceMap = aircraftDeviceMapper.getAircraftDeviceById(deviceIds);
// 按型号分组统计数量处理设备不存在的情况
Map<String, Long> 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<String, Double> 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<OrderDetail> fetchOrderDetailsByOrderIds(List<Long> orderIds) {
// 参数校验
if (orderIds == null || orderIds.isEmpty()) {
throw new IllegalArgumentException("订单ID列表不能为空");
}
// 分批查询每批1000个ID防止SQL IN子句过长
final int BATCH_SIZE = 1000;
List<OrderDetail> result = new ArrayList<>();
for (List<Long> batchIds : partitionList(orderIds, BATCH_SIZE)) {
List<OrderDetail> batchResult = orderDetailMapper.getOrderDetailsByOrderIds(batchIds);
if (batchResult != null) {
result.addAll(batchResult);
}
}
return result;
}
/**
* 将列表按指定大小分割为多个子列表
*
* @param list 原始列表
* @param batchSize 每个子列表的最大大小
* @return 子列表集合
*/
private <T> List<List<T>> partitionList(List<T> 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<String, Long> calculateRouteDistribution(List<OrderMain> orders) {
Map<String, Long> 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<String, Long> calculateRegionDistribution(List<OrderMain> orders) {
// if (orders == null || orders.isEmpty()) {
// return Collections.emptyMap();
// }
//
// // 提取所有订单中的区域ID去重
// List<Long> regionIds = orders.stream()
// .map(OrderMain::getAttractionId) // 获取区域id
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toList());
//
// // 通过区域ID查询区域名称映射
// Map<Long, String> 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 <T> OrderAnalysisResult<T> buildEmptyResult() {
OrderAnalysisResult<T> 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;
}
}

View File

@ -18,4 +18,6 @@ public interface CpRouteMapper extends BaseMapper<CpRoute> {
void updateDelFlagById(Long id, Integer delFlag);
CpRoute getRouteById(Integer id);
String getNameById(Long routeId);
}

View File

@ -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;
/**
* <p>
@ -53,4 +54,5 @@ public interface EmAreaService extends IService<EmArea> {
*/
List<EmArea> findByIdIn(List<Integer> collect);
Map<Long, String> getNameMapByIds(List<Long> regionIds);
}

View File

@ -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;
/**
* <p>
@ -71,6 +76,31 @@ public class EmAreaServiceImpl extends ServiceImpl<EmAreaMapper, EmArea> impleme
return super.list(wrapper);
}
@Override
public Map<Long, String> getNameMapByIds(List<Long> regionIds) {
if (regionIds == null || regionIds.isEmpty()) {
return Collections.emptyMap();
}
// 使用MyBatis-Plus的条件构造器查询区域信息
LambdaQueryWrapper<EmArea> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(EmArea::getId, regionIds);
// 执行查询
List<EmArea> areas = list(queryWrapper);
// 将结果转换为Map<Long, String>
return areas.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(
area -> area.getId().longValue(), // 区域ID
EmArea::getName, // 区域名称
(existing, replacement) -> existing, // 处理重复键的策略
LinkedHashMap::new // 使用有序Map保持插入顺序
));
}
/**
* 构建查询
*

View File

@ -16,4 +16,22 @@
WHERE name = #{name}
AND valid = 't'
</select>
<!-- 定义返回Map的结果映射 -->
<resultMap id="IdToNameMapResult" type="java.util.HashMap">
<result column="id" property="key" javaType="java.lang.Long" />
<result column="name" property="value" javaType="java.lang.String" />
</resultMap>
<select id="getNameMapByIds" resultMap="IdToNameMapResult">
SELECT
id,
name
FROM em_area
WHERE id IN
<foreach collection="regionIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND del_flag = 0
</select>
</mapper>

View File

@ -1,5 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aircraft.modules.aircraft.mapper.AircraftDeviceMapper">
<!-- 飞行器设备基础映射 -->
<resultMap id="BaseResultMap" type="com.aircraft.modules.aircraft.domain.AircraftDevice">
<id column="id" property="id" jdbcType="BIGINT" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="model" property="model" jdbcType="VARCHAR" />
<result column="brand" property="brand" jdbcType="VARCHAR" />
<result column="use_type" property="useType" jdbcType="INTEGER" />
<result column="area_id" property="areaId" jdbcType="BIGINT" />
<result column="scenic_id" property="scenicId" jdbcType="BIGINT" />
<result column="employees_id" property="employeesId" jdbcType="BIGINT" />
<result column="remark" property="remark" jdbcType="VARCHAR" />
<!-- 继承BaseEntity的公共字段映射 -->
<result column="create_by" property="createBy" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="update_by" property="updateBy" jdbcType="VARCHAR" />
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
</resultMap>
<!-- 通过设备ID列表查询飞行器设备 -->
<select id="getAircraftDeviceById" resultMap="BaseResultMap">
SELECT
id,
name,
model,
brand,
use_type,
area_id,
scenic_id,
employees_id,
remark,
create_by,
create_time,
update_by,
update_time
FROM fms_ac_aircraft_device
WHERE id IN
<foreach collection="deviceIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 结果处理器将List转换为Map<Long, AircraftDevice> -->
<resultMap id="DeviceMapResultMap" type="java.util.HashMap">
<result column="id" property="key" javaType="java.lang.Long" />
<association property="value" resultMap="BaseResultMap" />
</resultMap>
</mapper>

View File

@ -1,5 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aircraft.modules.order.mapper.OrderDetailMapper">
<!-- 订单详情基础映射 -->
<resultMap id="BaseResultMap" type="com.aircraft.modules.order.domain.OrderDetail">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="order_id" property="orderId" jdbcType="BIGINT"/>
<result column="device_id" property="deviceId" jdbcType="BIGINT"/>
<result column="person_conut" property="personConut" jdbcType="VARCHAR"/>
<result column="operator_id" property="operatorId" jdbcType="BIGINT"/>
<result column="order_item_status" property="orderItemStatus" jdbcType="INTEGER"/>
<result column="create_by" property="createBy" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="del_flag" property="delFlag" jdbcType="INTEGER"/>
</resultMap>
<!-- 通过订单ID列表查询订单详情列表 -->
<select id="getOrderDetailsByOrderIds" resultMap="BaseResultMap">
SELECT
id,
order_id,
device_id,
person_conut,
operator_id,
order_item_status,
create_by,
create_time,
update_by,
update_time,
del_flag
FROM fms_od_order_detail
WHERE order_id IN
<foreach collection="orderIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
ORDER BY order_id ASC, id ASC
</select>
<!-- 通过订单id获取单个订单详情-->
<select id="getOrderDetailsByOrderId" resultMap="BaseResultMap">
SELECT id,
order_id,
device_id,
person_conut,
operator_id,
order_item_status,
create_by,
create_time,
update_by,
update_time,
del_flag
FROM fms_od_order_detail
WHERE order_id = #{orderId}
ORDER BY fms_od_order_detail.create_time ASC
</select>
</mapper>

View File

@ -14,4 +14,9 @@
WHERE id = #{id}
AND del_flag = 0 <!-- 确保只更新未删除的记录 -->
</update>
<select id="getNameById" resultType="java.lang.String">
SELECT name
FROM cp_route
WHERE id = #{routeId}
</select>
</mapper>