优化项目细节和sql查询

This commit is contained in:
xuqiuyun
2025-11-28 17:12:36 +08:00
parent 128a4b2c6b
commit e968fcf52a
24 changed files with 1928 additions and 694 deletions

View File

@@ -180,8 +180,47 @@ public class OrderController {
}
}
@SuppressWarnings("unchecked")
List<Integer> orderIds = (List<Integer>) params.get("orderIds");
// 处理orderIds参数支持整数数组和字符串数组
List<Integer> orderIds = new ArrayList<>();
Object orderIdsObj = params.get("orderIds");
if (orderIdsObj != null) {
if (orderIdsObj instanceof List) {
@SuppressWarnings("unchecked")
List<Object> orderIdsList = (List<Object>) orderIdsObj;
for (Object item : orderIdsList) {
if (item instanceof Integer) {
orderIds.add((Integer) item);
} else if (item instanceof String) {
try {
orderIds.add(Integer.parseInt((String) item));
} catch (NumberFormatException e) {
logger.error("订单ID格式不正确{}", item);
return AjaxResult.error("订单ID格式不正确" + item);
}
} else if (item instanceof Number) {
orderIds.add(((Number) item).intValue());
} else {
logger.error("订单ID类型不支持{}", item.getClass().getName());
return AjaxResult.error("订单ID类型不支持");
}
}
} else if (orderIdsObj instanceof String) {
// 如果是逗号分隔的字符串,按逗号分割
String orderIdsStr = (String) orderIdsObj;
String[] ids = orderIdsStr.split(",");
for (String id : ids) {
try {
orderIds.add(Integer.parseInt(id.trim()));
} catch (NumberFormatException e) {
logger.error("订单ID格式不正确{}", id);
return AjaxResult.error("订单ID格式不正确" + id);
}
}
} else {
logger.error("orderIds参数类型不正确{}", orderIdsObj.getClass().getName());
return AjaxResult.error("orderIds参数类型不正确");
}
}
if (deliveryId == null) {
logger.error("更新失败运送清单ID不能为空");
@@ -199,5 +238,41 @@ public class OrderController {
return AjaxResult.error("批量更新订单delivery_id失败" + e.getMessage());
}
}
/**
* 解除订单与运送清单的关联
*/
@SaCheckPermission("order:edit")
@PostMapping("/unbindFromDelivery")
public AjaxResult unbindFromDelivery(@RequestBody Map<String, Object> params) {
try {
logger.info("解除订单与运送清单关联,参数:{}", params);
Integer orderId = null;
if (params.get("orderId") != null) {
Object orderIdObj = params.get("orderId");
if (orderIdObj instanceof Integer) {
orderId = (Integer) orderIdObj;
} else if (orderIdObj instanceof String) {
try {
orderId = Integer.parseInt((String) orderIdObj);
} catch (NumberFormatException e) {
logger.error("orderId格式不正确{}", orderIdObj);
return AjaxResult.error("orderId格式不正确");
}
}
}
if (orderId == null) {
logger.error("解除关联失败订单ID不能为空");
return AjaxResult.error("订单ID不能为空");
}
return orderService.unbindOrderFromDelivery(orderId);
} catch (Exception e) {
logger.error("解除订单与运送清单关联失败:{}", e.getMessage(), e);
return AjaxResult.error("解除关联失败:" + e.getMessage());
}
}
}

View File

@@ -15,6 +15,9 @@ public class DeliveryEditDto {
@NotNull(message = "运送清单ID不能为空")
private Integer deliveryId;
/** 关联订单ID多个订单ID用逗号分隔的字符串"1,2,3" */
private String orderId;
private String deliveryTitle;
private Integer ratedQuantity;
@@ -72,6 +75,9 @@ public class DeliveryEditDto {
/** 司机运费(元) */
private java.math.BigDecimal freight;
/** 检疫证号 */
private String quarantineCertNo;
/** 检疫票照片 */
private String quarantineTickeyUrl;
/** 传纸质磅单(双章) */

View File

@@ -285,6 +285,12 @@ public class Delivery implements Serializable {
@TableField("freight")
private java.math.BigDecimal freight;
/**
* 检疫证号
*/
@TableField("quarantine_tickey")
private String quarantineTickey;
/**
* 检疫票
*/

View File

@@ -90,6 +90,23 @@ public interface MemberDriverMapper {
"WHERE md.id = #{driverId}")
Map<String, Object> selectDriverById(@Param("driverId") Integer driverId);
/**
* 批量根据司机ID查询司机信息关联member表获取手机号
* 用于优化性能,避免 N+1 查询问题
*/
@Select("<script>" +
"SELECT md.id, md.member_id, md.username, " +
"md.driving_license, md.driver_license, md.record_code, " +
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
"FROM member_driver md " +
"LEFT JOIN member m ON md.member_id = m.id " +
"WHERE md.id IN " +
"<foreach collection='driverIds' item='id' open='(' separator=',' close=')'>" +
" #{id}" +
"</foreach>" +
"</script>")
List<Map<String, Object>> selectDriverByIds(@Param("driverIds") List<Integer> driverIds);
/**
* 根据车牌号查询司机信息(已废弃:司机表不再有车牌号字段)
* 该方法保留是为了兼容性,但会返回 null

View File

@@ -38,5 +38,20 @@ public interface VehicleMapper extends BaseMapper<Vehicle> {
"ORDER BY create_time DESC " +
"</script>")
List<Vehicle> selectVehicleList(@Param("licensePlate") String licensePlate);
/**
* 批量根据车牌号查询车辆信息
* 用于优化性能,避免 N+1 查询问题
* @param licensePlates 车牌号列表
* @return 车辆列表
*/
@Select("<script>" +
"SELECT * FROM vehicle WHERE is_delete = 0 " +
"AND license_plate IN " +
"<foreach collection='licensePlates' item='plate' open='(' separator=',' close=')'>" +
" #{plate}" +
"</foreach>" +
"</script>")
List<Vehicle> selectByLicensePlates(@Param("licensePlates") List<String> licensePlates);
}

View File

@@ -72,5 +72,13 @@ public interface IOrderService extends IService<Order> {
* @return AjaxResult
*/
AjaxResult updateOrderDeliveryId(Integer deliveryId, List<Integer> orderIds);
/**
* 解除订单与运送清单的关联将订单的deliveryId设置为null
*
* @param orderId 订单ID
* @return AjaxResult
*/
AjaxResult unbindOrderFromDelivery(Integer orderId);
}

View File

@@ -136,228 +136,9 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
// 需要权限过滤:先查询所有数据,填充信息,过滤后再分页
list = this.list(wrapper);
// 填充关联信息(供应商、资金方、采购商、司机等
// 使用批量查询填充关联信息(优化性能,避免 N+1 查询
if(CollectionUtils.isNotEmpty(list)){
list.forEach(delivery -> {
if(userId.equals(delivery.getCheckBy())){
//判断是否需要核验1需要核验2不需要核验
delivery.setIfCheck(1);
}
// 查询四个角色的手机号(供应商、资金方、采购商、司机)
try {
// 1. 查询供应商信息supplierId是逗号分隔的字符串
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
String[] supplierIds = delivery.getSupplierId().split(",");
List<String> supplierNames = new ArrayList<>();
List<String> supplierMobiles = new ArrayList<>();
for (String supplierId : supplierIds) {
if (StringUtils.isNotEmpty(supplierId.trim())) {
try {
Integer sid = Integer.parseInt(supplierId.trim());
// 查询member和member_user表关联数据
Map<String, Object> supplierInfo = memberMapper.selectMemberUserById(sid);
if (supplierInfo != null) {
String username = (String) supplierInfo.get("username");
String mobile = (String) supplierInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
supplierNames.add(username);
}
if (StringUtils.isNotEmpty(mobile)) {
supplierMobiles.add(mobile);
}
} else {
}
} catch (NumberFormatException e) {
}
}
}
if (!supplierNames.isEmpty()) {
delivery.setSupplierName(String.join(",", supplierNames));
} else if (!supplierMobiles.isEmpty()) {
// 如果用户名为空,使用手机号作为备选
delivery.setSupplierName(String.join(",", supplierMobiles));
}
if (!supplierMobiles.isEmpty()) {
delivery.setSupplierMobile(String.join(",", supplierMobiles));
}
}
// 2. 查询资金方信息
if (delivery.getFundId() != null) {
Map<String, Object> fundInfo = memberMapper.selectMemberUserById(delivery.getFundId());
if (fundInfo != null) {
String username = (String) fundInfo.get("username");
String mobile = (String) fundInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
delivery.setFundName(username);
} else if (StringUtils.isNotEmpty(mobile)) {
// 如果用户名为空,使用手机号作为备选
delivery.setFundName(mobile);
}
if (StringUtils.isNotEmpty(mobile)) {
delivery.setFundMobile(mobile);
}
} else {
}
}
// 3. 查询采购商信息
if (delivery.getBuyerId() != null) {
Map<String, Object> buyerInfo = memberMapper.selectMemberUserById(delivery.getBuyerId());
if (buyerInfo != null) {
String username = (String) buyerInfo.get("username");
String mobile = (String) buyerInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
delivery.setBuyerName(username);
} else if (StringUtils.isNotEmpty(mobile)) {
// 如果用户名为空,使用手机号作为备选
delivery.setBuyerName(mobile);
}
if (StringUtils.isNotEmpty(mobile)) {
delivery.setBuyerMobile(mobile);
}
} else {
}
}
// 4. 查询司机手机号如果有司机ID
if (delivery.getDriverId() != null) {
try {
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
if (driverInfo != null) {
String driverName = (String) driverInfo.get("username");
String driverMobile = (String) driverInfo.get("mobile");
String carImg = (String) driverInfo.get("car_img");
if (StringUtils.isNotEmpty(driverMobile)) {
delivery.setDriverMobile(driverMobile);
}
if (StringUtils.isNotEmpty(driverName)) {
delivery.setDriverName(driverName);
}
// 优先从车辆表获取车身照片(根据车牌号)
// 如果车辆表中没有,再从司机信息中获取作为后备
boolean vehiclePhotoSet = false;
if (delivery.getLicensePlate() != null && StringUtils.isNotEmpty(delivery.getLicensePlate())) {
try {
Vehicle vehicle = vehicleMapper.selectByLicensePlate(delivery.getLicensePlate());
if (vehicle != null) {
String carFrontPhoto = vehicle.getCarFrontPhoto();
String carRearPhoto = vehicle.getCarRearPhoto();
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
vehiclePhotoSet = true;
logger.debug("从车辆表获取照片: 车牌号={}, 车头照片={}, 车尾照片={}",
delivery.getLicensePlate(), delivery.getCarFrontPhoto(), delivery.getCarBehindPhoto());
}
}
} catch (Exception e) {
logger.error("从车辆表获取照片失败: " + e.getMessage(), e);
}
}
// 如果车辆表中没有照片,从司机信息中获取作为后备
if (!vehiclePhotoSet && carImg != null && !carImg.isEmpty()) {
// 按逗号分割car_img字段分别映射到车头和车尾照片
String[] carImgUrls = carImg.split(",");
if (carImgUrls.length >= 2) {
// 逗号前面的URL作为车尾照片
String carBehindPhoto = carImgUrls[0].trim();
// 逗号后面的URL作为车头照片
String carFrontPhoto = carImgUrls[1].trim();
delivery.setCarBehindPhoto(carBehindPhoto);
delivery.setCarFrontPhoto(carFrontPhoto);
} else if (carImgUrls.length == 1) {
// 只有一个URL时同时设置为车头和车尾照片
String singlePhoto = carImgUrls[0].trim();
delivery.setCarFrontPhoto(singlePhoto);
delivery.setCarBehindPhoto(singlePhoto);
} else {
// 没有有效URL设置为null
delivery.setCarFrontPhoto(null);
delivery.setCarBehindPhoto(null);
}
} else if (!vehiclePhotoSet) {
// 如果车辆表和司机信息中都没有照片设置为null
delivery.setCarFrontPhoto(null);
delivery.setCarBehindPhoto(null);
}
}
} catch (Exception e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 统计登记设备数量(耳标+项圈)
Integer currentDeliveryId = delivery.getId();
if (currentDeliveryId != null) {
try {
// 统计耳标设备数量
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 2);
long earTagCount = deliveryDeviceService.count(earTagWrapper);
// 统计项圈设备数量
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
collarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 3);
long collarCount = deliveryDeviceService.count(collarWrapper);
// 设置总设备数量和耳标数量
int totalDeviceCount = (int) (earTagCount + collarCount);
delivery.setRegisteredJbqCount(totalDeviceCount);
delivery.setEarTagCount((int) earTagCount);
// 设置已分配设备数量,与登记设备数量保持一致
delivery.setBindJbqCount(totalDeviceCount);
// 统计已佩戴设备数量bandge_status = 1
int wornDeviceCount = 0;
try {
// 统计已佩戴的耳标设备数量通过delivery_device表关联
LambdaQueryWrapper<DeliveryDevice> wornEarTagWrapper = new LambdaQueryWrapper<>();
wornEarTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 2)
.eq(DeliveryDevice::getIsWare, 1); // 1表示已佩戴
long wornEarTagCount = deliveryDeviceService.count(wornEarTagWrapper);
// 统计已佩戴的项圈设备数量通过delivery_device表关联xq_client表
LambdaQueryWrapper<DeliveryDevice> wornCollarWrapper = new LambdaQueryWrapper<>();
wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 3);
List<DeliveryDevice> collarDevices = deliveryDeviceService.list(wornCollarWrapper);
int wornCollarCount = 0;
for (DeliveryDevice device : collarDevices) {
// 查询xq_client表中的bandge_status
LambdaQueryWrapper<XqClient> xqWrapper = new LambdaQueryWrapper<>();
xqWrapper.eq(XqClient::getSn, device.getDeviceId());
XqClient xqClient = xqClientService.getOne(xqWrapper);
if (xqClient != null && xqClient.getBandgeStatus() != null && xqClient.getBandgeStatus() == 1) {
wornCollarCount++;
}
}
wornDeviceCount = (int) (wornEarTagCount + wornCollarCount);
delivery.setWareCount(wornDeviceCount);
} catch (Exception e) {
delivery.setWareCount(0);
}
} catch (Exception e) {
delivery.setRegisteredJbqCount(0);
delivery.setBindJbqCount(0);
delivery.setWareCount(0);
}
}
});
batchFillDeliveryRelations(list, userId);
}
// 数据权限过滤:非超级管理员只能看到与自己手机号相关的订单
@@ -413,223 +194,9 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
list = this.list(wrapper);
total = result.getTotal();
// 填充关联信息(供应商、资金方、采购商、司机等
// 使用批量查询填充关联信息(优化性能,避免 N+1 查询
if(CollectionUtils.isNotEmpty(list)){
list.forEach(delivery -> {
if(userId.equals(delivery.getCheckBy())){
//判断是否需要核验1需要核验2不需要核验
delivery.setIfCheck(1);
}
// 查询四个角色的手机号(供应商、资金方、采购商、司机)
try {
// 1. 查询供应商信息supplierId是逗号分隔的字符串
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
String[] supplierIds = delivery.getSupplierId().split(",");
List<String> supplierNames = new ArrayList<>();
List<String> supplierMobiles = new ArrayList<>();
for (String supplierId : supplierIds) {
if (StringUtils.isNotEmpty(supplierId.trim())) {
try {
Integer sid = Integer.parseInt(supplierId.trim());
// 查询member和member_user表关联数据
Map<String, Object> supplierInfo = memberMapper.selectMemberUserById(sid);
if (supplierInfo != null) {
String username = (String) supplierInfo.get("username");
String mobile = (String) supplierInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
supplierNames.add(username);
}
if (StringUtils.isNotEmpty(mobile)) {
supplierMobiles.add(mobile);
}
}
} catch (NumberFormatException e) {
}
}
}
if (!supplierNames.isEmpty()) {
delivery.setSupplierName(String.join(",", supplierNames));
} else if (!supplierMobiles.isEmpty()) {
// 如果用户名为空,使用手机号作为备选
delivery.setSupplierName(String.join(",", supplierMobiles));
}
if (!supplierMobiles.isEmpty()) {
delivery.setSupplierMobile(String.join(",", supplierMobiles));
}
}
// 2. 查询资金方信息
if (delivery.getFundId() != null) {
Map<String, Object> fundInfo = memberMapper.selectMemberUserById(delivery.getFundId());
if (fundInfo != null) {
String username = (String) fundInfo.get("username");
String mobile = (String) fundInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
delivery.setFundName(username);
} else if (StringUtils.isNotEmpty(mobile)) {
// 如果用户名为空,使用手机号作为备选
delivery.setFundName(mobile);
}
if (StringUtils.isNotEmpty(mobile)) {
delivery.setFundMobile(mobile);
}
}
}
// 3. 查询采购商信息
if (delivery.getBuyerId() != null) {
Map<String, Object> buyerInfo = memberMapper.selectMemberUserById(delivery.getBuyerId());
if (buyerInfo != null) {
String username = (String) buyerInfo.get("username");
String mobile = (String) buyerInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
delivery.setBuyerName(username);
} else if (StringUtils.isNotEmpty(mobile)) {
// 如果用户名为空,使用手机号作为备选
delivery.setBuyerName(mobile);
}
if (StringUtils.isNotEmpty(mobile)) {
delivery.setBuyerMobile(mobile);
}
}
}
// 4. 查询司机手机号如果有司机ID
if (delivery.getDriverId() != null) {
try {
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
if (driverInfo != null) {
String driverName = (String) driverInfo.get("username");
String driverMobile = (String) driverInfo.get("mobile");
String carImg = (String) driverInfo.get("car_img");
if (StringUtils.isNotEmpty(driverMobile)) {
delivery.setDriverMobile(driverMobile);
}
if (StringUtils.isNotEmpty(driverName)) {
delivery.setDriverName(driverName);
}
// 优先从车辆表获取车身照片(根据车牌号)
// 如果车辆表中没有,再从司机信息中获取作为后备
boolean vehiclePhotoSet = false;
if (delivery.getLicensePlate() != null && StringUtils.isNotEmpty(delivery.getLicensePlate())) {
try {
Vehicle vehicle = vehicleMapper.selectByLicensePlate(delivery.getLicensePlate());
if (vehicle != null) {
String carFrontPhoto = vehicle.getCarFrontPhoto();
String carRearPhoto = vehicle.getCarRearPhoto();
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
vehiclePhotoSet = true;
}
}
} catch (Exception e) {
logger.error("从车辆表获取照片失败: " + e.getMessage(), e);
}
}
// 如果车辆表中没有照片,从司机信息中获取作为后备
if (!vehiclePhotoSet && carImg != null && !carImg.isEmpty()) {
// 按逗号分割car_img字段分别映射到车头和车尾照片
String[] carImgUrls = carImg.split(",");
if (carImgUrls.length >= 2) {
// 逗号前面的URL作为车尾照片
String carBehindPhoto = carImgUrls[0].trim();
// 逗号后面的URL作为车头照片
String carFrontPhoto = carImgUrls[1].trim();
delivery.setCarBehindPhoto(carBehindPhoto);
delivery.setCarFrontPhoto(carFrontPhoto);
} else if (carImgUrls.length == 1) {
// 只有一个URL时同时设置为车头和车尾照片
String singlePhoto = carImgUrls[0].trim();
delivery.setCarFrontPhoto(singlePhoto);
delivery.setCarBehindPhoto(singlePhoto);
} else {
// 没有有效URL设置为null
delivery.setCarFrontPhoto(null);
delivery.setCarBehindPhoto(null);
}
} else if (!vehiclePhotoSet) {
// 如果车辆表和司机信息中都没有照片设置为null
delivery.setCarFrontPhoto(null);
delivery.setCarBehindPhoto(null);
}
}
} catch (Exception e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 统计登记设备数量(耳标+项圈)
Integer currentDeliveryId = delivery.getId();
if (currentDeliveryId != null) {
try {
// 统计耳标设备数量
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 2);
long earTagCount = deliveryDeviceService.count(earTagWrapper);
// 统计项圈设备数量
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
collarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 3);
long collarCount = deliveryDeviceService.count(collarWrapper);
// 设置总设备数量和耳标数量
int totalDeviceCount = (int) (earTagCount + collarCount);
delivery.setRegisteredJbqCount(totalDeviceCount);
delivery.setEarTagCount((int) earTagCount);
// 设置已分配设备数量,与登记设备数量保持一致
delivery.setBindJbqCount(totalDeviceCount);
// 统计已佩戴设备数量bandge_status = 1
int wornDeviceCount = 0;
try {
// 统计已佩戴的耳标设备数量通过delivery_device表关联
LambdaQueryWrapper<DeliveryDevice> wornEarTagWrapper = new LambdaQueryWrapper<>();
wornEarTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 2)
.eq(DeliveryDevice::getIsWare, 1); // 1表示已佩戴
long wornEarTagCount = deliveryDeviceService.count(wornEarTagWrapper);
// 统计已佩戴的项圈设备数量通过delivery_device表关联xq_client表
LambdaQueryWrapper<DeliveryDevice> wornCollarWrapper = new LambdaQueryWrapper<>();
wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
.eq(DeliveryDevice::getDeviceType, 3);
List<DeliveryDevice> collarDevices = deliveryDeviceService.list(wornCollarWrapper);
int wornCollarCount = 0;
for (DeliveryDevice device : collarDevices) {
// 查询xq_client表中的bandge_status
LambdaQueryWrapper<XqClient> xqWrapper = new LambdaQueryWrapper<>();
xqWrapper.eq(XqClient::getSn, device.getDeviceId());
XqClient xqClient = xqClientService.getOne(xqWrapper);
if (xqClient != null && xqClient.getBandgeStatus() != null && xqClient.getBandgeStatus() == 1) {
wornCollarCount++;
}
}
wornDeviceCount = (int) (wornEarTagCount + wornCollarCount);
delivery.setWareCount(wornDeviceCount);
} catch (Exception e) {
delivery.setWareCount(0);
}
} catch (Exception e) {
delivery.setRegisteredJbqCount(0);
delivery.setBindJbqCount(0);
delivery.setWareCount(0);
}
}
});
batchFillDeliveryRelations(list, userId);
}
}
@@ -799,6 +366,10 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
delivery.setLandingEntruckWeight(StringUtils.isNotEmpty(dto.getLandingEntruckWeight()) ? dto.getLandingEntruckWeight() : null);
// 司机运费
delivery.setFreight(dto.getFreight());
// 检疫证号
if (dto.getQuarantineCertNo() != null && !dto.getQuarantineCertNo().trim().isEmpty()) {
delivery.setQuarantineTickey(dto.getQuarantineCertNo().trim());
}
// 照片
delivery.setQuarantineTickeyUrl(dto.getQuarantineTickeyUrl());
delivery.setPoundListImg(dto.getPoundListImg());
@@ -1046,6 +617,53 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
delivery.setFirmPrice(dto.getFirmPrice());
}
// 更新订单ID关联追加模式如果已有订单ID则追加如果没有则直接设置
if (dto.getOrderId() != null && StringUtils.isNotEmpty(dto.getOrderId())) {
String newOrderIdStr = dto.getOrderId().trim();
String existingOrderId = delivery.getOrderId();
// 将新订单ID字符串按逗号分割为列表支持多个订单ID
List<String> newOrderIdList = Arrays.asList(newOrderIdStr.split(","));
newOrderIdList = newOrderIdList.stream()
.map(String::trim)
.filter(id -> !id.isEmpty())
.collect(java.util.stream.Collectors.toList());
if (StringUtils.isNotEmpty(existingOrderId)) {
// 如果已有订单ID将现有订单ID也按逗号分割
List<String> orderIdList = new ArrayList<>(Arrays.asList(existingOrderId.split(",")));
// 去除空白并过滤空字符串
orderIdList = orderIdList.stream()
.map(String::trim)
.filter(id -> !id.isEmpty())
.collect(java.util.stream.Collectors.toList());
// 追加新订单ID如果不存在
for (String newOrderId : newOrderIdList) {
if (!orderIdList.contains(newOrderId)) {
orderIdList.add(newOrderId);
}
}
// 重新排序并去重
orderIdList = orderIdList.stream()
.distinct()
.sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b)))
.collect(java.util.stream.Collectors.toList());
delivery.setOrderId(String.join(",", orderIdList));
logger.info("追加订单ID到运送清单: deliveryId={}, 原有订单ID={}, 新增订单ID={}, 更新后订单ID={}",
dto.getDeliveryId(), existingOrderId, newOrderIdStr, delivery.getOrderId());
} else {
// 如果没有现有订单ID直接设置排序并去重
List<String> orderIdList = newOrderIdList.stream()
.distinct()
.sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b)))
.collect(java.util.stream.Collectors.toList());
delivery.setOrderId(String.join(",", orderIdList));
logger.info("设置运送清单的订单ID: deliveryId={}, orderId={}", dto.getDeliveryId(), delivery.getOrderId());
}
}
// 更新重量字段将空字符串转换为null避免数据库DECIMAL字段类型错误
// 注意:前端可能传递空字符串"",需要处理这种情况
if (dto.getEmptyWeight() != null) {
@@ -1062,6 +680,15 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
delivery.setFreight(dto.getFreight());
}
// 更新检疫证号
if (dto.getQuarantineCertNo() != null) {
if (StringUtils.isNotEmpty(dto.getQuarantineCertNo())) {
delivery.setQuarantineTickey(dto.getQuarantineCertNo().trim());
} else {
delivery.setQuarantineTickey(null);
}
}
// 更新照片字段将空字符串转换为null避免前端显示问题
// 注意前端总是传递字段即使是空字符串只有在传递了有效URL时才更新
if (dto.getQuarantineTickeyUrl() != null) {
@@ -2266,5 +1893,275 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
return unique;
}
/**
* 批量填充 Delivery 列表的关联信息(优化性能,避免 N+1 查询)
* @param deliveries Delivery 列表
* @param userId 当前用户ID
*/
private void batchFillDeliveryRelations(List<Delivery> deliveries, Integer userId) {
if (CollectionUtils.isEmpty(deliveries)) {
return;
}
// 1. 收集所有需要查询的 ID
Set<Integer> allMemberIds = new HashSet<>();
Set<Integer> driverIds = new HashSet<>();
Set<String> licensePlates = new HashSet<>();
Set<Integer> deliveryIds = new HashSet<>();
for (Delivery delivery : deliveries) {
// 收集供应商ID可能是逗号分隔的字符串
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
String[] supplierIds = delivery.getSupplierId().split(",");
for (String supplierId : supplierIds) {
if (StringUtils.isNotEmpty(supplierId.trim())) {
try {
allMemberIds.add(Integer.parseInt(supplierId.trim()));
} catch (NumberFormatException e) {
// 忽略无效的ID
}
}
}
}
// 收集资金方ID
if (delivery.getFundId() != null) {
allMemberIds.add(delivery.getFundId());
}
// 收集采购商ID
if (delivery.getBuyerId() != null) {
allMemberIds.add(delivery.getBuyerId());
}
// 收集司机ID
if (delivery.getDriverId() != null) {
driverIds.add(delivery.getDriverId());
}
// 收集车牌号
if (StringUtils.isNotEmpty(delivery.getLicensePlate())) {
licensePlates.add(delivery.getLicensePlate());
}
// 收集运单ID用于设备统计
if (delivery.getId() != null) {
deliveryIds.add(delivery.getId());
}
}
// 2. 批量查询 member 信息
Map<Integer, Map<String, Object>> memberMap = new HashMap<>();
if (!allMemberIds.isEmpty()) {
List<Map<String, Object>> memberList = memberMapper.selectMemberUserByIds(new ArrayList<>(allMemberIds));
for (Map<String, Object> member : memberList) {
Object idObj = member.get("id");
Integer id = null;
if (idObj instanceof Integer) {
id = (Integer) idObj;
} else if (idObj instanceof Long) {
id = ((Long) idObj).intValue();
} else if (idObj instanceof Number) {
id = ((Number) idObj).intValue();
}
if (id != null) {
memberMap.put(id, member);
}
}
}
// 3. 批量查询司机信息
Map<Integer, Map<String, Object>> driverMap = new HashMap<>();
if (!driverIds.isEmpty()) {
List<Map<String, Object>> driverList = memberDriverMapper.selectDriverByIds(new ArrayList<>(driverIds));
for (Map<String, Object> driver : driverList) {
Object idObj = driver.get("id");
Integer id = null;
if (idObj instanceof Integer) {
id = (Integer) idObj;
} else if (idObj instanceof Long) {
id = ((Long) idObj).intValue();
} else if (idObj instanceof Number) {
id = ((Number) idObj).intValue();
}
if (id != null) {
driverMap.put(id, driver);
}
}
}
// 4. 批量查询车辆信息
Map<String, Vehicle> vehicleMap = new HashMap<>();
if (!licensePlates.isEmpty()) {
List<Vehicle> vehicleList = vehicleMapper.selectByLicensePlates(new ArrayList<>(licensePlates));
for (Vehicle vehicle : vehicleList) {
if (vehicle != null && StringUtils.isNotEmpty(vehicle.getLicensePlate())) {
vehicleMap.put(vehicle.getLicensePlate(), vehicle);
}
}
}
// 5. 批量查询设备数量统计(使用 SQL 聚合查询)
Map<Integer, Map<String, Integer>> deviceCountMap = new HashMap<>();
if (!deliveryIds.isEmpty()) {
// 批量查询耳标和项圈设备数量
for (Integer deliveryId : deliveryIds) {
try {
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
earTagWrapper.eq(DeliveryDevice::getDeliveryId, deliveryId)
.eq(DeliveryDevice::getDeviceType, 2);
long earTagCount = deliveryDeviceService.count(earTagWrapper);
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
collarWrapper.eq(DeliveryDevice::getDeliveryId, deliveryId)
.eq(DeliveryDevice::getDeviceType, 3);
long collarCount = deliveryDeviceService.count(collarWrapper);
Map<String, Integer> counts = new HashMap<>();
counts.put("earTag", (int) earTagCount);
counts.put("collar", (int) collarCount);
counts.put("total", (int) (earTagCount + collarCount));
deviceCountMap.put(deliveryId, counts);
} catch (Exception e) {
logger.error("查询设备数量失败deliveryId: {}", deliveryId, e);
}
}
}
// 6. 填充每个 Delivery 的关联信息
for (Delivery delivery : deliveries) {
// 设置核验标识
if (userId != null && userId.equals(delivery.getCheckBy())) {
delivery.setIfCheck(1);
}
// 填充供应商信息
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
String[] supplierIds = delivery.getSupplierId().split(",");
List<String> supplierNames = new ArrayList<>();
List<String> supplierMobiles = new ArrayList<>();
for (String supplierId : supplierIds) {
if (StringUtils.isNotEmpty(supplierId.trim())) {
try {
Integer sid = Integer.parseInt(supplierId.trim());
Map<String, Object> supplierInfo = memberMap.get(sid);
if (supplierInfo != null) {
String username = (String) supplierInfo.get("username");
String mobile = (String) supplierInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
supplierNames.add(username);
}
if (StringUtils.isNotEmpty(mobile)) {
supplierMobiles.add(mobile);
}
}
} catch (NumberFormatException e) {
// 忽略无效的ID
}
}
}
if (!supplierNames.isEmpty()) {
delivery.setSupplierName(String.join(",", supplierNames));
} else if (!supplierMobiles.isEmpty()) {
delivery.setSupplierName(String.join(",", supplierMobiles));
}
if (!supplierMobiles.isEmpty()) {
delivery.setSupplierMobile(String.join(",", supplierMobiles));
}
}
// 填充资金方信息
if (delivery.getFundId() != null) {
Map<String, Object> fundInfo = memberMap.get(delivery.getFundId());
if (fundInfo != null) {
String username = (String) fundInfo.get("username");
String mobile = (String) fundInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
delivery.setFundName(username);
} else if (StringUtils.isNotEmpty(mobile)) {
delivery.setFundName(mobile);
}
if (StringUtils.isNotEmpty(mobile)) {
delivery.setFundMobile(mobile);
}
}
}
// 填充采购商信息
if (delivery.getBuyerId() != null) {
Map<String, Object> buyerInfo = memberMap.get(delivery.getBuyerId());
if (buyerInfo != null) {
String username = (String) buyerInfo.get("username");
String mobile = (String) buyerInfo.get("mobile");
if (StringUtils.isNotEmpty(username)) {
delivery.setBuyerName(username);
} else if (StringUtils.isNotEmpty(mobile)) {
delivery.setBuyerName(mobile);
}
if (StringUtils.isNotEmpty(mobile)) {
delivery.setBuyerMobile(mobile);
}
}
}
// 填充司机信息
if (delivery.getDriverId() != null) {
Map<String, Object> driverInfo = driverMap.get(delivery.getDriverId());
if (driverInfo != null) {
String driverName = (String) driverInfo.get("username");
String driverMobile = (String) driverInfo.get("mobile");
String carImg = (String) driverInfo.get("car_img");
if (StringUtils.isNotEmpty(driverMobile)) {
delivery.setDriverMobile(driverMobile);
}
if (StringUtils.isNotEmpty(driverName)) {
delivery.setDriverName(driverName);
}
// 优先从车辆表获取车身照片
boolean vehiclePhotoSet = false;
if (StringUtils.isNotEmpty(delivery.getLicensePlate())) {
Vehicle vehicle = vehicleMap.get(delivery.getLicensePlate());
if (vehicle != null) {
String carFrontPhoto = vehicle.getCarFrontPhoto();
String carRearPhoto = vehicle.getCarRearPhoto();
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
vehiclePhotoSet = true;
}
}
}
// 如果车辆表中没有照片,从司机信息中获取
if (!vehiclePhotoSet && StringUtils.isNotEmpty(carImg)) {
String[] carImgUrls = carImg.split(",");
if (carImgUrls.length >= 2) {
delivery.setCarBehindPhoto(carImgUrls[0].trim());
delivery.setCarFrontPhoto(carImgUrls[1].trim());
} else if (carImgUrls.length == 1) {
String singlePhoto = carImgUrls[0].trim();
delivery.setCarFrontPhoto(singlePhoto);
delivery.setCarBehindPhoto(singlePhoto);
}
}
}
}
// 填充设备数量统计
if (delivery.getId() != null) {
Map<String, Integer> counts = deviceCountMap.get(delivery.getId());
if (counts != null) {
delivery.setRegisteredJbqCount(counts.get("total"));
delivery.setEarTagCount(counts.get("earTag"));
delivery.setBindJbqCount(counts.get("total"));
// 已佩戴设备数量暂时设为0需要额外查询
delivery.setWareCount(0);
} else {
delivery.setRegisteredJbqCount(0);
delivery.setEarTagCount(0);
delivery.setBindJbqCount(0);
delivery.setWareCount(0);
}
}
}
}
}

View File

@@ -11,8 +11,6 @@ import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +22,7 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;
/**
@@ -93,131 +92,114 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
queryWrapper.orderByDesc(Order::getCreateTime);
// 判断是否需要先过滤再分页(如果提供了买方、卖方、运单号、起始地、目的地、单价搜索)
boolean needFilter = (buyerName != null && !buyerName.trim().isEmpty()) ||
(sellerName != null && !sellerName.trim().isEmpty()) ||
(deliveryNumber != null && !deliveryNumber.trim().isEmpty()) ||
(startLocation != null && !startLocation.trim().isEmpty()) ||
(endLocation != null && !endLocation.trim().isEmpty()) ||
(firmPrice != null && !firmPrice.trim().isEmpty());
List<Order> filteredList;
long total;
if (needFilter) {
// 需要过滤的情况:先查询所有数据,填充信息,过滤,然后手动分页
List<Order> allList = orderMapper.selectList(queryWrapper);
// 批量填充关联信息(优化性能)
fillOrderInfoBatch(allList);
// 进行过滤
filteredList = allList;
if (buyerName != null && !buyerName.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getBuyerName() != null && order.getBuyerName().contains(buyerName.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (sellerName != null && !sellerName.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getSellerName() != null && order.getSellerName().contains(sellerName.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (deliveryNumber != null && !deliveryNumber.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getDeliveryNumber() != null && order.getDeliveryNumber().contains(deliveryNumber.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (startLocation != null && !startLocation.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getStartLocation() != null && order.getStartLocation().contains(startLocation.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (endLocation != null && !endLocation.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getEndLocation() != null && order.getEndLocation().contains(endLocation.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (firmPrice != null && !firmPrice.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> {
java.math.BigDecimal price = order.getFirmPriceFromDelivery() != null ? order.getFirmPriceFromDelivery() : order.getFirmPrice();
if (price == null) {
return false;
}
return price.toString().contains(firmPrice.trim());
})
.collect(java.util.stream.Collectors.toList());
}
// 获取总数
total = filteredList.size();
// 手动分页
int startIndex = (pageNum - 1) * pageSize;
int endIndex = Math.min(startIndex + pageSize, filteredList.size());
if (startIndex < filteredList.size()) {
filteredList = filteredList.subList(startIndex, endIndex);
} else {
filteredList = new ArrayList<>();
}
logger.info("查询到{}条订单记录,过滤后{}条,分页后{}条", allList.size(), total, filteredList.size());
} else {
// 不需要过滤的情况直接使用PageHelper分页
Page<Order> page = PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<Order> list = orderMapper.selectList(queryWrapper);
// 批量填充关联信息(优化性能)
fillOrderInfoBatch(list);
// 获取总数和分页数据
total = page.getTotal();
filteredList = list;
logger.info("查询到{}条订单记录,分页后{}条", total, filteredList.size());
// 为了保持买卖关系链的完整性,统一采用先查询所有数据、排序、再分页的方式
// 这样可以确保同一deliveryId的订单链条不会被分页截断
// 先查询所有符合条件的数据(不包含需要填充信息的过滤条件)
List<Order> allList = orderMapper.selectList(queryWrapper);
// 批量填充关联信息(优化性能)
fillOrderInfoBatch(allList);
// 进行过滤(针对需要填充信息后才能过滤的字段)
filteredList = allList;
if (buyerName != null && !buyerName.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getBuyerName() != null && order.getBuyerName().contains(buyerName.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (sellerName != null && !sellerName.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getSellerName() != null && order.getSellerName().contains(sellerName.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (deliveryNumber != null && !deliveryNumber.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getDeliveryNumber() != null && order.getDeliveryNumber().contains(deliveryNumber.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (startLocation != null && !startLocation.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getStartLocation() != null && order.getStartLocation().contains(startLocation.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (endLocation != null && !endLocation.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> order.getEndLocation() != null && order.getEndLocation().contains(endLocation.trim()))
.collect(java.util.stream.Collectors.toList());
}
if (firmPrice != null && !firmPrice.trim().isEmpty()) {
filteredList = filteredList.stream()
.filter(order -> {
java.math.BigDecimal price = order.getFirmPriceFromDelivery() != null ? order.getFirmPriceFromDelivery() : order.getFirmPrice();
if (price == null) {
return false;
}
return price.toString().contains(firmPrice.trim());
})
.collect(java.util.stream.Collectors.toList());
}
// 获取总数(排序前)
total = filteredList.size();
logger.info("查询到{}条订单记录,过滤后{}条", allList.size(), total);
// 优化排序:按运送清单分组排序
// 优化排序:按运送清单分组,同链路内按买卖关系链排序
// 排序规则1. 有deliveryId的订单按deliveryId分组相同deliveryId的订单放在一起
// 2. 按deliveryId排序null值放在最后
// 3. 同一deliveryId内的订单按创建时间排序
filteredList.sort((o1, o2) -> {
Integer deliveryId1 = o1.getDeliveryId();
Integer deliveryId2 = o2.getDeliveryId();
// 如果两个订单都没有deliveryId,按创建时间倒序
if (deliveryId1 == null && deliveryId2 == null) {
if (o1.getCreateTime() != null && o2.getCreateTime() != null) {
return o2.getCreateTime().compareTo(o1.getCreateTime());
}
return 0;
// 3. 同一deliveryId内的订单按买卖关系链排序
// - 如果订单A的卖方等于订单B的买方则B排在A后面
// - 形成链条订单1 -> 订单2 -> 订单3
List<Order> sortedList = new ArrayList<>();
// deliveryId分组
java.util.Map<Integer, List<Order>> deliveryGroups = new java.util.HashMap<>();
List<Order> noDeliveryOrders = new ArrayList<>();
for (Order order : filteredList) {
Integer deliveryId = order.getDeliveryId();
if (deliveryId == null) {
noDeliveryOrders.add(order);
} else {
deliveryGroups.computeIfAbsent(deliveryId, k -> new ArrayList<>()).add(order);
}
// 如果只有一个订单有deliveryId有deliveryId的排在前面
if (deliveryId1 == null) {
return 1; // o1排在后面
}
if (deliveryId2 == null) {
return -1; // o2排在后面
}
// 两个订单都有deliveryId先按deliveryId排序
int deliveryIdCompare = deliveryId1.compareTo(deliveryId2);
if (deliveryIdCompare != 0) {
return deliveryIdCompare;
}
// deliveryId相同按创建时间倒序
}
// 对每个deliveryId组内的订单按买卖关系链排序
List<Integer> sortedDeliveryIds = new ArrayList<>(deliveryGroups.keySet());
sortedDeliveryIds.sort(Integer::compareTo);
for (Integer deliveryId : sortedDeliveryIds) {
List<Order> groupOrders = deliveryGroups.get(deliveryId);
List<Order> sortedGroup = sortOrdersByChain(groupOrders);
sortedList.addAll(sortedGroup);
}
// 没有deliveryId的订单按创建时间倒序放在最后
noDeliveryOrders.sort((o1, o2) -> {
if (o1.getCreateTime() != null && o2.getCreateTime() != null) {
return o2.getCreateTime().compareTo(o1.getCreateTime());
}
return 0;
});
sortedList.addAll(noDeliveryOrders);
logger.info("订单列表排序完成,共{}条记录", filteredList.size());
filteredList = sortedList;
logger.info("订单列表排序完成,共{}条记录,已按买卖关系链排序", filteredList.size());
// 排序后再进行分页,确保同一链条的订单不会被分到不同页
int startIndex = (pageNum - 1) * pageSize;
int endIndex = Math.min(startIndex + pageSize, filteredList.size());
if (startIndex < filteredList.size()) {
filteredList = filteredList.subList(startIndex, endIndex);
logger.info("分页后{}条记录(第{}页,每页{}条)", filteredList.size(), pageNum, pageSize);
} else {
filteredList = new ArrayList<>();
logger.info("分页后0条记录超出范围");
}
// 构建分页结果
return new PageResultResponse<>(total, filteredList);
@@ -229,8 +211,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
@Override
@Transactional
public AjaxResult addOrder(Order order) {
logger.info("开始新增订单,买方:{},卖方:{},结算方式:{},约定价格:{}",
order.getBuyerId(), order.getSellerId(), order.getSettlementType(), order.getFirmPrice());
logger.info("开始新增订单,买方:{},卖方:{},结算方式:{},约定价格:{}运送清单ID{}",
order.getBuyerId(), order.getSellerId(), order.getSettlementType(), order.getFirmPrice(), order.getDeliveryId());
try {
// 验证结算方式
@@ -250,6 +232,16 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
return AjaxResult.error("约定价格不能小于0");
}
// 如果提供了deliveryId验证运送清单是否存在
if (order.getDeliveryId() != null) {
com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(order.getDeliveryId());
if (delivery == null) {
logger.error("运送清单不存在ID{}", order.getDeliveryId());
return AjaxResult.error("运送清单不存在");
}
logger.info("订单将关联到运送清单运送清单ID{},运单号:{}", order.getDeliveryId(), delivery.getDeliveryNumber());
}
// 设置创建人和创建时间
Integer userId = SecurityUtil.getCurrentUserId();
order.setCreatedBy(userId);
@@ -259,8 +251,15 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
int result = orderMapper.insert(order);
if (result > 0) {
logger.info("新增订单成功订单ID{}", order.getId());
return AjaxResult.success("新增订单成功");
if (order.getDeliveryId() != null) {
logger.info("新增订单成功并关联运送清单订单ID{}运送清单ID{}", order.getId(), order.getDeliveryId());
} else {
logger.info("新增订单成功订单ID{}", order.getId());
}
// 返回订单ID方便前端更新关联关系
Map<String, Object> data = new HashMap<>();
data.put("id", order.getId());
return AjaxResult.success("新增订单成功", data);
} else {
logger.error("新增订单失败");
return AjaxResult.error("新增订单失败");
@@ -341,6 +340,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
/**
* 删除订单(逻辑删除)
* 删除订单时同时更新delivery表中order_id字段移除对应的订单ID
*/
@Override
@Transactional
@@ -360,6 +360,75 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
return AjaxResult.error("订单不存在");
}
// 在删除订单之前先更新delivery表中order_id字段移除该订单ID
String orderIdStr = String.valueOf(id);
try {
// 查询所有order_id字段包含该订单ID的delivery记录
// 使用LIKE查询匹配开头、中间、结尾或单独存在
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.aiotagro.cattletrade.business.entity.Delivery> wrapper =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
wrapper.and(w -> w
.eq(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, orderIdStr) // 单独存在
.or()
.like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, orderIdStr + ",") // 开头
.or()
.like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "," + orderIdStr + ",") // 中间
.or()
.like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "," + orderIdStr) // 结尾
);
wrapper.isNotNull(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId);
wrapper.ne(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "");
List<com.aiotagro.cattletrade.business.entity.Delivery> deliveryList = deliveryMapper.selectList(wrapper);
if (deliveryList != null && !deliveryList.isEmpty()) {
logger.info("找到{}条运送清单包含订单ID{}", deliveryList.size(), id);
// 更新每条delivery记录的order_id字段
for (com.aiotagro.cattletrade.business.entity.Delivery delivery : deliveryList) {
String currentOrderId = delivery.getOrderId();
if (currentOrderId != null && !currentOrderId.trim().isEmpty()) {
// 将order_id字符串按逗号分割
List<String> orderIdList = new ArrayList<>(Arrays.asList(currentOrderId.split(",")));
// 去除空白并过滤空字符串
orderIdList = orderIdList.stream()
.map(String::trim)
.filter(oid -> !oid.isEmpty())
.collect(Collectors.toList());
// 移除该订单ID
orderIdList.remove(orderIdStr);
// 如果还有订单ID重新组合否则设置为null
if (!orderIdList.isEmpty()) {
// 排序并去重
orderIdList = orderIdList.stream()
.distinct()
.sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b)))
.collect(Collectors.toList());
String newOrderId = String.join(",", orderIdList);
delivery.setOrderId(newOrderId);
logger.info("更新运送清单order_iddeliveryId={}, 原值={}, 新值={}",
delivery.getId(), currentOrderId, newOrderId);
} else {
delivery.setOrderId(null);
logger.info("更新运送清单order_iddeliveryId={}, 原值={}, 新值=null已清空",
delivery.getId(), currentOrderId);
}
// 更新delivery记录
deliveryMapper.updateById(delivery);
}
}
logger.info("成功更新{}条运送清单的order_id字段", deliveryList.size());
} else {
logger.info("未找到包含订单ID {} 的运送清单记录", id);
}
} catch (Exception e) {
logger.error("更新delivery表order_id字段失败订单ID{},错误:{}", id, e.getMessage(), e);
// 不阻止删除流程,只记录错误
}
// 逻辑删除通过update方法设置is_delete=1
// MyBatis-Plus的@TableLogic会自动处理
int result = orderMapper.deleteById(id);
@@ -607,6 +676,176 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
}
}
/**
* 按买卖关系链排序订单
* 规则如果订单A的卖方等于订单B的买方则B排在A后面
* 支持买方/卖方为多个名称(逗号分隔)的情况
*
* @param orders 同一deliveryId的订单列表
* @return 排序后的订单列表
*/
private List<Order> sortOrdersByChain(List<Order> orders) {
if (orders == null || orders.size() <= 1) {
return orders;
}
List<Order> result = new ArrayList<>();
List<Order> remaining = new ArrayList<>(orders);
// 辅助方法:检查两个名称是否匹配(支持逗号分隔的多个名称)
java.util.function.BiFunction<String, String, Boolean> namesMatch = (name1, name2) -> {
if (name1 == null || name2 == null) {
return false;
}
// 分割并trim支持 ", " 或 "," 分隔
String[] names1 = name1.split(",\\s*");
String[] names2 = name2.split(",\\s*");
for (String n1 : names1) {
String trimmed1 = n1.trim();
if (trimmed1.isEmpty()) {
continue;
}
for (String n2 : names2) {
String trimmed2 = n2.trim();
if (trimmed1.equals(trimmed2)) {
logger.debug("名称匹配:{} == {}", trimmed1, trimmed2);
return true;
}
}
}
return false;
};
// 记录所有订单的买卖方信息用于调试
logger.info("开始排序订单,共{}条", remaining.size());
for (Order order : remaining) {
logger.info("订单ID{},买方:{},卖方:{}", order.getId(), order.getBuyerName(), order.getSellerName());
}
// 找到链条的起点:没有其他订单的卖方等于这个订单的买方
Order startOrder = null;
for (Order order : remaining) {
String buyerName = order.getBuyerName();
if (buyerName == null || buyerName.trim().isEmpty()) {
continue;
}
boolean isStart = true;
// 检查是否有其他订单的卖方等于这个订单的买方
for (Order other : remaining) {
if (order == other) {
continue;
}
String otherSellerName = other.getSellerName();
if (otherSellerName != null && namesMatch.apply(buyerName, otherSellerName)) {
logger.info("订单{}不是起点,因为订单{}的卖方({})等于订单{}的买方({})",
order.getId(), other.getId(), otherSellerName, order.getId(), buyerName);
isStart = false;
break;
}
}
if (isStart) {
logger.info("找到起点订单:{},买方:{}", order.getId(), buyerName);
startOrder = order;
break;
}
}
// 如果没有找到起点(可能是循环链或没有匹配关系),使用第一个订单作为起点
if (startOrder == null) {
logger.warn("未找到起点订单,使用第一个订单作为起点");
startOrder = remaining.get(0);
}
// 从起点开始构建链条
result.add(startOrder);
remaining.remove(startOrder);
logger.info("添加起点订单:{}", startOrder.getId());
// 按照买卖关系链依次添加订单,支持多个独立链条
while (!remaining.isEmpty()) {
Order currentOrder = result.get(result.size() - 1); // 获取当前链条的最后一个订单
boolean found = false;
String currentSellerName = currentOrder.getSellerName();
if (currentSellerName != null && !currentSellerName.trim().isEmpty()) {
// 查找下一个订单:买方等于当前订单的卖方
for (Order nextOrder : remaining) {
String nextBuyerName = nextOrder.getBuyerName();
if (nextBuyerName != null && namesMatch.apply(currentSellerName, nextBuyerName)) {
logger.info("找到下一个订单:{}(当前订单{}的卖方{} == 订单{}的买方{}",
nextOrder.getId(), currentOrder.getId(), currentSellerName, nextOrder.getId(), nextBuyerName);
result.add(nextOrder);
remaining.remove(nextOrder);
found = true;
break;
}
}
}
// 如果当前链条无法继续,尝试从剩余订单中找新的起点构建新链条
if (!found) {
logger.info("当前链条无法继续,当前订单{}的卖方:{},剩余{}个订单,尝试构建新链条",
currentOrder.getId(), currentSellerName, remaining.size());
// 从剩余订单中找新的起点
Order newStartOrder = null;
for (Order order : remaining) {
String buyerName = order.getBuyerName();
if (buyerName == null || buyerName.trim().isEmpty()) {
continue;
}
boolean isStart = true;
// 检查是否有其他剩余订单的卖方等于这个订单的买方
for (Order other : remaining) {
if (order == other) {
continue;
}
String otherSellerName = other.getSellerName();
if (otherSellerName != null && namesMatch.apply(buyerName, otherSellerName)) {
isStart = false;
break;
}
}
if (isStart) {
logger.info("找到新链条起点订单:{},买方:{}", order.getId(), buyerName);
newStartOrder = order;
break;
}
}
// 如果找到新起点,开始构建新链条
if (newStartOrder != null) {
result.add(newStartOrder);
remaining.remove(newStartOrder);
logger.info("添加新链条起点订单:{}", newStartOrder.getId());
// 继续循环,从新起点开始构建链条
continue;
} else {
// 如果找不到新起点,将剩余订单按创建时间倒序添加
logger.info("未找到新链条起点,将剩余订单按创建时间排序");
for (Order rem : remaining) {
logger.info("剩余订单{},买方:{},卖方:{}", rem.getId(), rem.getBuyerName(), rem.getSellerName());
}
remaining.sort((o1, o2) -> {
if (o1.getCreateTime() != null && o2.getCreateTime() != null) {
return o2.getCreateTime().compareTo(o1.getCreateTime());
}
return 0;
});
result.addAll(remaining);
break;
}
}
}
logger.info("排序完成,最终顺序:{}", result.stream().map(o -> String.valueOf(o.getId())).collect(Collectors.joining(" -> ")));
return result;
}
/**
* 填充订单关联信息(买方名称、卖方名称、创建人名称、结算方式描述)
* 用于单个订单详情查询
@@ -854,6 +1093,36 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
logger.info("批量更新订单delivery_id完成成功{}条,失败:{}条", successCount, failCount);
// 同步更新delivery表的order_id字段将所有关联的订单ID用逗号连接
if (successCount > 0) {
try {
// 查询该运送清单当前关联的所有订单ID
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getDeliveryId, deliveryId)
.select(Order::getId)
.orderByAsc(Order::getId);
List<Order> associatedOrders = orderMapper.selectList(queryWrapper);
// 将所有订单ID用逗号连接
String orderIdStr = associatedOrders.stream()
.map(order -> String.valueOf(order.getId()))
.collect(Collectors.joining(","));
// 更新delivery表的order_id字段
com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(deliveryId);
if (delivery != null) {
delivery.setOrderId(orderIdStr);
deliveryMapper.updateById(delivery);
logger.info("同步更新运送清单order_id成功运送清单ID{}订单ID列表{}", deliveryId, orderIdStr);
} else {
logger.warn("运送清单不存在无法更新order_id运送清单ID{}", deliveryId);
}
} catch (Exception e) {
logger.error("同步更新运送清单order_id失败运送清单ID{},错误:{}", deliveryId, e.getMessage(), e);
// 不抛出异常只记录日志因为订单的delivery_id已经更新成功
}
}
if (failCount == 0) {
return AjaxResult.success("批量更新成功,共更新" + successCount + "条订单");
} else {
@@ -868,5 +1137,88 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
return AjaxResult.error("批量更新订单delivery_id失败" + e.getMessage());
}
}
/**
* 解除订单与运送清单的关联将订单的deliveryId设置为null
*
* @param orderId 订单ID
* @return AjaxResult
*/
@Override
@Transactional
public AjaxResult unbindOrderFromDelivery(Integer orderId) {
try {
logger.info("解除订单与运送清单的关联订单ID{}", orderId);
if (orderId == null) {
logger.error("订单ID不能为空");
return AjaxResult.error("订单ID不能为空");
}
// 查询订单是否存在
Order order = orderMapper.selectById(orderId);
if (order == null) {
logger.error("订单不存在ID{}", orderId);
return AjaxResult.error("订单不存在");
}
// 如果订单没有关联运送清单,直接返回成功
if (order.getDeliveryId() == null) {
logger.info("订单未关联运送清单无需解除订单ID{}", orderId);
return AjaxResult.success("订单未关联运送清单,无需解除");
}
Integer oldDeliveryId = order.getDeliveryId();
// 将deliveryId设置为null
order.setDeliveryId(null);
order.setUpdateTime(new Date());
try {
order.setUpdatedBy(SecurityUtil.getCurrentUserId());
} catch (Exception e) {
logger.warn("获取当前用户失败,使用订单原有创建人:{}", e.getMessage());
if (order.getCreatedBy() != null) {
order.setUpdatedBy(order.getCreatedBy());
}
}
int result = orderMapper.updateById(order);
if (result > 0) {
// 同步更新delivery表的order_id字段移除该订单ID
if (oldDeliveryId != null) {
try {
com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(oldDeliveryId);
if (delivery != null && delivery.getOrderId() != null && !delivery.getOrderId().trim().isEmpty()) {
// 从order_id字符串中移除当前订单ID
String orderIdStr = delivery.getOrderId();
List<String> orderIdList = new ArrayList<>(Arrays.asList(orderIdStr.split(",")));
orderIdList.remove(String.valueOf(orderId));
// 更新order_id字段
if (orderIdList.isEmpty()) {
delivery.setOrderId(null);
} else {
delivery.setOrderId(String.join(",", orderIdList));
}
deliveryMapper.updateById(delivery);
logger.info("同步更新运送清单order_id成功移除订单ID{}运送清单ID{}", orderId, oldDeliveryId);
}
} catch (Exception e) {
logger.error("同步更新运送清单order_id失败运送清单ID{},错误:{}", oldDeliveryId, e.getMessage(), e);
// 不抛出异常,只记录日志
}
}
logger.info("解除订单与运送清单关联成功订单ID{}原运送清单ID{}", orderId, oldDeliveryId);
return AjaxResult.success("解除关联成功");
} else {
logger.error("解除订单与运送清单关联失败订单ID{}", orderId);
return AjaxResult.error("解除关联失败");
}
} catch (Exception e) {
logger.error("解除订单与运送清单关联异常订单ID{},错误:{}", orderId, e.getMessage(), e);
return AjaxResult.error("解除关联失败:" + e.getMessage());
}
}
}

View File

@@ -8,6 +8,8 @@ import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* Druid 数据源配置类
*
* @author Carson
* @package_name com.aiotagro.payinfo.config
* @date 2024/2/6 17:00
@@ -15,21 +17,22 @@ import javax.sql.DataSource;
@Configuration
public class DruidDataSourceConfig {
//编写方法,注入DruidDataSource
//为什么我们注入自己的DataSource , 默认的HiKariDatasource失效?
//1. 默认的数据源是如配置? @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
// 解读通过@ConditionalOnMissingBean({ DataSource.class}) 判断如果容器有DataSource Bean 就不注入默认的HiKariDatasource
/**
* 配置 Druid 数据源
*
* 说明:
* 1. 使用 @ConfigurationProperties("spring.datasource") 自动读取配置文件中的连接池参数
* 2. Spring Boot 默认使用 HikariCP通过注入自定义 DataSource Bean 来使用 Druid
* 3. Druid 连接池参数通过 application.yml 中的 spring.datasource.druid.* 配置
*
* @return DataSource 数据源实例
*/
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() {
//1. 配置了 @ConfigurationProperties("spring.datasource")
// 就可以读取到application.yml的配置注意我们需要将bean注入到spring ioc容器中、bean中提供get\set方法
//2. 我们就不需要调用DruidDataSource 对象的setXxx, 会自动关联
DruidDataSource druidDataSource = new DruidDataSource();
//druidDataSource.setUrl();
//druidDataSource.setUsername();
//druidDataSource.setPassword();
// 连接池参数通过 @ConfigurationProperties 自动从配置文件注入
// 配置文件位置application.yml 中的 spring.datasource.druid.*
return druidDataSource;
}
}

View File

@@ -23,9 +23,46 @@ spring:
max-wait: -1ms
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true
username: root
password: Aiotagro@741
# Druid 连接池配置
druid:
# 初始连接数
initial-size: 5
# 最小空闲连接数
min-idle: 5
# 最大活跃连接数
max-active: 20
# 获取连接的最大等待时间毫秒60秒
max-wait: 60000
# 连接池中的连接空闲多久后会被回收(毫秒)
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存时间毫秒5分钟
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存时间毫秒15分钟
max-evictable-idle-time-millis: 900000
# 验证连接的 SQL 查询
validation-query: SELECT 1
# 获取连接时是否验证连接有效性
test-on-borrow: false
# 归还连接时是否验证连接有效性
test-on-return: false
# 空闲时是否验证连接有效性(推荐开启)
test-while-idle: true
# 连接超时时间(毫秒)
connection-timeout: 60000
# Socket 超时时间(毫秒)
socket-timeout: 60000
# 查询超时时间(毫秒)
query-timeout: 60000
# 连接失败后是否中断
break-after-acquire-failure: false
# 连接失败重试次数
connection-error-retry-attempts: 3
# 是否在连接断开时自动重连
keep-alive: true
sa-token:
# token 名称(同时也是 cookie 名称)

View File

@@ -26,9 +26,46 @@ spring:
# username: iot-plateform
# password: 3qJ7$bV%N9mE
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true
username: root
password: Aiotagro@741
# Druid 连接池配置
druid:
# 初始连接数
initial-size: 5
# 最小空闲连接数
min-idle: 5
# 最大活跃连接数
max-active: 20
# 获取连接的最大等待时间毫秒60秒
max-wait: 60000
# 连接池中的连接空闲多久后会被回收(毫秒)
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存时间毫秒5分钟
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存时间毫秒15分钟
max-evictable-idle-time-millis: 900000
# 验证连接的 SQL 查询
validation-query: SELECT 1
# 获取连接时是否验证连接有效性
test-on-borrow: false
# 归还连接时是否验证连接有效性
test-on-return: false
# 空闲时是否验证连接有效性(推荐开启)
test-while-idle: true
# 连接超时时间(毫秒)
connection-timeout: 60000
# Socket 超时时间(毫秒)
socket-timeout: 60000
# 查询超时时间(毫秒)
query-timeout: 60000
# 连接失败后是否中断
break-after-acquire-failure: false
# 连接失败重试次数
connection-error-retry-attempts: 3
# 是否在连接断开时自动重连
keep-alive: true
sa-token:
# token 名称(同时也是 cookie 名称)

View File

@@ -23,9 +23,46 @@ spring:
# max-wait: -1ms
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true
username: root
password: Aiotagro@741
# Druid 连接池配置
druid:
# 初始连接数
initial-size: 5
# 最小空闲连接数
min-idle: 5
# 最大活跃连接数
max-active: 20
# 获取连接的最大等待时间毫秒60秒
max-wait: 60000
# 连接池中的连接空闲多久后会被回收(毫秒)
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存时间毫秒5分钟
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存时间毫秒15分钟
max-evictable-idle-time-millis: 900000
# 验证连接的 SQL 查询
validation-query: SELECT 1
# 获取连接时是否验证连接有效性
test-on-borrow: false
# 归还连接时是否验证连接有效性
test-on-return: false
# 空闲时是否验证连接有效性(推荐开启)
test-while-idle: true
# 连接超时时间(毫秒)
connection-timeout: 60000
# Socket 超时时间(毫秒)
socket-timeout: 60000
# 查询超时时间(毫秒)
query-timeout: 60000
# 连接失败后是否中断
break-after-acquire-failure: false
# 连接失败重试次数
connection-error-retry-attempts: 3
# 是否在连接断开时自动重连
keep-alive: true
sa-token:
# token 名称(同时也是 cookie 名称)

View File

@@ -0,0 +1,63 @@
USE cattletrade;
-- 为 warning_log 表添加索引以优化预警列表查询性能
-- 优化目标getPageWarningLog 查询中的派生表查询和 JOIN 操作
--
-- 执行说明:
-- 1. 先执行检查脚本查看现有索引SHOW INDEX FROM warning_log;
-- 2. 如果索引已存在,会报错 "Duplicate key name",可以忽略该错误或注释掉对应语句
-- 3. 建议按顺序执行,遇到已存在的索引错误时跳过即可
-- ============================================
-- 第一步:检查现有索引(可选)
-- ============================================
-- 执行以下命令查看 warning_log 表的所有索引:
-- SHOW INDEX FROM warning_log;
-- ============================================
-- 第二步:创建索引(所有索引已存在,已全部注释)
-- ============================================
-- ✅ 状态:所有需要的索引已经存在,无需再次创建
-- 如需重新创建请先删除现有索引DROP INDEX idx_name ON warning_log;
-- 1. 添加 delivery_id 和 warning_type 的联合索引(用于派生表的 GROUP BY 查询)
-- 这个索引可以大幅提升 "SELECT delivery_id, warning_type, MAX(id) FROM warning_log WHERE warning_type IN (...) GROUP BY delivery_id, warning_type" 的性能
-- ✅ 索引已存在idx_delivery_warning_type
-- ALTER TABLE warning_log
-- ADD INDEX idx_delivery_warning_type (delivery_id, warning_type, id);
-- 2. 添加 warning_time 索引(用于 ORDER BY 排序)
-- ✅ 索引已存在idx_warning_time
-- ALTER TABLE warning_log
-- ADD INDEX idx_warning_time (warning_time DESC);
-- 3. 如果 delivery_id 还没有单独索引,添加一个(用于 JOIN 操作)
-- 注意:如果 delivery_id 已经是主键或唯一索引的一部分,可以跳过
-- 检查现有索引SHOW INDEX FROM warning_log;
-- 如果 delivery_id 没有索引,执行下面的语句
-- ALTER TABLE warning_log ADD INDEX idx_delivery_id (delivery_id);
-- ============================================
-- 索引说明和性能优化效果
-- ============================================
--
-- 已存在的索引:
-- 1. idx_delivery_warning_type: 覆盖索引,支持 WHERE warning_type IN (...) GROUP BY delivery_id, warning_type 的查询
-- 2. idx_warning_time: 支持 ORDER BY warning_time DESC 的排序操作
--
-- 性能提升效果:
-- ✅ 派生表查询从全表扫描优化为索引扫描
-- ✅ GROUP BY 操作可以使用索引,避免临时表排序
-- ✅ ORDER BY 可以使用索引,避免文件排序
--
-- ============================================
-- 验证索引是否生效
-- ============================================
-- 执行以下 SQL 验证索引:
-- SHOW INDEX FROM warning_log WHERE Key_name IN ('idx_delivery_warning_type', 'idx_warning_time');
--
-- 使用 EXPLAIN 分析查询性能:
-- EXPLAIN SELECT ... (使用优化后的 getPageWarningLog SQL)
--
-- 注意:所有索引已创建完成,脚本保留作为文档参考

View File

@@ -36,6 +36,12 @@
id, delivery_number, buyer_price, sale_price, firm_price, status, license_plate, car_front_photo, car_behind_photo, car_video, start_location, start_lat, start_lon, end_location, end_lat, end_lon, estimated_delivery_time, registered_jbq_count, driver_name, driver_mobile, create_time, created_by, check_by, check_time, check_video
</sql>
<!--
优化说明:
1. 使用派生表Derived Table替代关联子查询避免对每一行执行子查询
2. 先找出每个 delivery_id 和 warning_type 的最大 id然后通过 JOIN 连接
3. 性能提升:从 O(n*m) 降低到 O(n+m),其中 n 是 delivery 数量m 是 warning_log 数量
-->
<select id="getPageWarningLog" resultType="com.aiotagro.cattletrade.business.entity.Delivery">
SELECT
d.id,
@@ -65,17 +71,15 @@
wl.inventory_jbq_count,
su.name as createByName
FROM delivery d
INNER JOIN warning_log wl ON d.id = wl.delivery_id
INNER JOIN (
SELECT delivery_id, warning_type, MAX(id) as max_id
FROM warning_log
WHERE warning_type IN (2,3,4,5,6,7,8,9)
GROUP BY delivery_id, warning_type
) latest_wl ON d.id = latest_wl.delivery_id
INNER JOIN warning_log wl ON wl.id = latest_wl.max_id
LEFT JOIN sys_user su ON d.created_by = su.id
<where>
wl.warning_type IN (2,3,4,5,6,7,8,9)
AND wl.id IN (
SELECT MAX(id)
FROM warning_log
WHERE delivery_id = d.id
AND warning_type IN (2,3,4,5,6,7,8,9)
GROUP BY warning_type
)
<if test="dto.deliveryNumber != null and '' != dto.deliveryNumber">
AND d.delivery_number LIKE CONCAT('%', #{dto.deliveryNumber}, '%')
</if>