From 8e06aa3f684dd224bae6aad59b62b108f0897dbd Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Thu, 13 Aug 2026 20:58:52 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97bug=202=E3=80=81=E5=AE=8C=E5=96=84=E5=87=AD?= =?UTF-8?q?=E8=AF=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/vo/MasterOrderVO.java | 3 + .../config/VoucherImportRabbitConfig.java | 26 +++-- .../VoucherImportMessageListener.java | 23 ++++- .../VoucherUploadCompletedListener.java | 9 +- .../service/impl/MasterOrderServiceImpl.java | 94 ++++++++++++++----- .../impl/VoucherManageServiceImpl.java | 51 +++++++++- ...blade_waybill_non_road_fields_20260813.sql | 24 +++++ 7 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 doc/sql/transport/blade_waybill_non_road_fields_20260813.sql diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java index 76bda0c..490607a 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import lombok.Data; import lombok.EqualsAndHashCode; import org.springblade.transport.pojo.entity.MasterOrder; +import org.springblade.transport.pojo.entity.Waybill; import java.io.Serial; import java.math.BigDecimal; @@ -37,6 +38,8 @@ public class MasterOrderVO extends MasterOrder { @TableField(exist = false) private List> routeProgress; @TableField(exist = false) + private List boundWaybills; + @TableField(exist = false) private BigDecimal totalQuantity; @TableField(exist = false) private String createUserName; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherImportRabbitConfig.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherImportRabbitConfig.java index e784e56..09fee09 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherImportRabbitConfig.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherImportRabbitConfig.java @@ -10,6 +10,7 @@ import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.beans.factory.annotation.Value; /** * 凭证导入消息队列配置。 @@ -18,22 +19,35 @@ import org.springframework.context.annotation.Configuration; @Configuration public class VoucherImportRabbitConfig { - public static final String EXCHANGE = "tms.voucher.import.exchange"; - public static final String QUEUE = "tms.voucher.import.queue"; - public static final String ROUTING_KEY = "tms.voucher.import"; + private final String exchange; + private final String queue; + private final String routingKey; + + public VoucherImportRabbitConfig( + @Value("${voucher.import.rabbit.exchange:tms.voucher.import.exchange}") String exchange, + @Value("${voucher.import.rabbit.queue:tms.voucher.import.queue}") String queue, + @Value("${voucher.import.rabbit.routing-key:tms.voucher.import}") String routingKey) { + this.exchange = exchange; + this.queue = queue; + this.routingKey = routingKey; + } + + public String getExchange() { return exchange; } + public String getQueue() { return queue; } + public String getRoutingKey() { return routingKey; } @Bean public DirectExchange voucherImportExchange() { - return new DirectExchange(EXCHANGE, true, false); + return new DirectExchange(exchange, true, false); } @Bean public Queue voucherImportQueue() { - return new Queue(QUEUE, true); + return new Queue(queue, true); } @Bean public Binding voucherImportBinding(Queue voucherImportQueue, DirectExchange voucherImportExchange) { - return BindingBuilder.bind(voucherImportQueue).to(voucherImportExchange).with(ROUTING_KEY); + return BindingBuilder.bind(voucherImportQueue).to(voucherImportExchange).with(routingKey); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherImportMessageListener.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherImportMessageListener.java index 8b8f2bc..c97feb7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherImportMessageListener.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherImportMessageListener.java @@ -5,22 +5,41 @@ package org.springblade.transport.listener; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springblade.transport.config.VoucherImportRabbitConfig; import org.springblade.transport.service.IVoucherManageService; import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.listener.MessageListenerContainer; +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.stereotype.Component; +import org.springframework.context.event.EventListener; /** * 凭证压缩包后台处理消费者。 */ @Component @RequiredArgsConstructor +@Slf4j public class VoucherImportMessageListener { - private final IVoucherManageService voucherManageService; + private static final String LISTENER_ID = "voucherImportMessageListener"; - @RabbitListener(queues = VoucherImportRabbitConfig.QUEUE) + private final IVoucherManageService voucherManageService; + private final VoucherImportRabbitConfig voucherImportRabbitConfig; + private final RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry; + + @EventListener(ApplicationReadyEvent.class) + public void logConsumerStatus() { + MessageListenerContainer container = rabbitListenerEndpointRegistry.getListenerContainer(LISTENER_ID); + log.info("[凭证MQ] 消费者状态 listenerId={}, queue={}, registered={}, running={}", + LISTENER_ID, voucherImportRabbitConfig.getQueue(), container != null, container != null && container.isRunning()); + } + + @RabbitListener(id = LISTENER_ID, queues = "${voucher.import.rabbit.queue:tms.voucher.import.queue}") public void processVoucher(Long voucherId) { + log.info("[凭证MQ] 收到处理任务 queue={}, voucherId={}", voucherImportRabbitConfig.getQueue(), voucherId); voucherManageService.processUploadedVoucher(voucherId); + log.info("[凭证MQ] 处理任务完成 voucherId={}", voucherId); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherUploadCompletedListener.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherUploadCompletedListener.java index 792af9d..7af86a9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherUploadCompletedListener.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/VoucherUploadCompletedListener.java @@ -5,6 +5,7 @@ package org.springblade.transport.listener; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springblade.transport.config.VoucherImportRabbitConfig; import org.springblade.transport.event.VoucherUploadCompletedEvent; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -17,13 +18,17 @@ import org.springframework.transaction.event.TransactionalEventListener; */ @Component @RequiredArgsConstructor +@Slf4j public class VoucherUploadCompletedListener { private final RabbitTemplate rabbitTemplate; + private final VoucherImportRabbitConfig voucherImportRabbitConfig; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void publish(VoucherUploadCompletedEvent event) { - rabbitTemplate.convertAndSend(VoucherImportRabbitConfig.EXCHANGE, - VoucherImportRabbitConfig.ROUTING_KEY, event.getVoucherId()); + log.info("[凭证MQ] 投递处理任务 exchange={}, routingKey={}, voucherId={}", + voucherImportRabbitConfig.getExchange(), voucherImportRabbitConfig.getRoutingKey(), event.getVoucherId()); + rabbitTemplate.convertAndSend(voucherImportRabbitConfig.getExchange(), + voucherImportRabbitConfig.getRoutingKey(), event.getVoucherId()); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java index f9a2bac..52663ce 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java @@ -78,7 +78,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl boundWaybills = waybillsByMasterNo(entity.getMasterNo()); + vo.setBoundWaybills(boundWaybills); + vo.setRouteProgress(buildProgress(entity, vo.getRoutes(), boundWaybills)); vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser())); vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser())); return vo; } @@ -260,16 +263,18 @@ public class MasterOrderServiceImpl extends BaseServiceImpl> buildProgress(MasterOrder masterOrder, List> routes) { - List waybills = waybillService.list(new LambdaQueryWrapper().eq(Waybill::getMasterNo, masterOrder.getMasterNo())); + private List> buildProgress(MasterOrder masterOrder, List> routes, List waybills) { List plans = transportPlanService.list(new LambdaQueryWrapper().eq(TransportPlan::getMasterNo, masterOrder.getMasterNo())); for (Map route : routes) { String segmentNo = string(route, "segmentNo"); - route.put("dispatchedQuantity", dispatchedQuantity(masterOrder.getMasterNo(), segmentNo)); - route.put("dispatchedGoods", dispatchedGoods(masterOrder.getMasterNo(), segmentNo)); - route.put("arrivedQuantity", waybills.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo()) && "completed".equals(item.getBusinessStatus())).map(Waybill::getQuantity).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); - route.put("waybills", waybills.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo())).toList()); - route.put("transportPlans", plans.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo())).toList()); + List routeWaybills = waybills.stream().filter(item -> belongsToRoute(item, route)).toList(); + List routePlans = plans.stream().filter(item -> belongsToRoute(item, route)).toList(); + Map dispatchedGoods = dispatchedGoods(routeWaybills, routePlans); + route.put("dispatchedQuantity", dispatchedGoods.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add)); + route.put("dispatchedGoods", dispatchedGoods); + route.put("arrivedQuantity", routeWaybills.stream().filter(item -> "completed".equals(item.getBusinessStatus())).flatMap(item -> waybillGoods(item).stream()).map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add)); + route.put("waybills", routeWaybills); + route.put("transportPlans", routePlans); } return routes; } @@ -282,7 +287,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl dispatch) { String carrierType = string(dispatch, "carrierType", "承运商"); + boolean road = string(dispatch, "transportType", "").toLowerCase().contains("road") || string(dispatch, "transportType", "").contains("公路"); + if (!road) { + if (Func.isEmpty(string(dispatch, "vehicleNo")) || Func.isEmpty(string(dispatch, "captainName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "containerNo")) || Func.isEmpty(string(dispatch, "cabinNo")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) <= 0 || ("承运商".equals(carrierType) && Func.isEmpty(string(dispatch, "carrierName")))) { + throw new ServiceException("非公路运输的承运信息不完整"); + } + return; + } if (Func.isEmpty(string(dispatch, "vehicleNo"))) throw new ServiceException("运单车牌号不能为空"); if ("承运商".equals(carrierType)) { - if (Func.isEmpty(string(dispatch, "carrierName"))) throw new ServiceException("运单承运商不能为空"); + if (Func.isEmpty(string(dispatch, "carrierName")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) <= 0) throw new ServiceException("承运商、里程不能为空且里程必须为正整数"); return; } if (Func.isEmpty(string(dispatch, "driverName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "trailerVehicleNo")) || Func.isEmpty(string(dispatch, "escortName")) || Func.isEmpty(string(dispatch, "escortPhone")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) < 0) throw new ServiceException("自运或网货平台的车辆与人员信息不完整"); @@ -350,19 +362,57 @@ public class MasterOrderServiceImpl extends BaseServiceImpl dispatchedGoods(String masterNo, String segmentNo) { - Map result = new LinkedHashMap<>(); - LambdaQueryWrapper billQuery = new LambdaQueryWrapper().eq(Waybill::getMasterNo, masterNo); - if (Func.isNotEmpty(segmentNo)) billQuery.eq(Waybill::getRelationNo, segmentNo); - for (Waybill bill : waybillService.list(billQuery)) { - List> goods = parseArray(bill.getGoodsJson()); - if (goods.isEmpty()) goods = List.of(Map.of("cargoName", bill.getCargoName(), "cargoType", bill.getCargoType(), "quantity", bill.getQuantity())); - for (Map goodsItem : goods) result.merge(goodsKey(goodsItem), decimal(goodsItem, "quantity"), BigDecimal::add); - } + List waybills = waybillsByMasterNo(masterNo).stream().filter(item -> Func.isEmpty(segmentNo) || Objects.equals(segmentNo, item.getRelationNo())).toList(); LambdaQueryWrapper planQuery = new LambdaQueryWrapper().eq(TransportPlan::getMasterNo, masterNo); if (Func.isNotEmpty(segmentNo)) planQuery.eq(TransportPlan::getRelationNo, segmentNo); - for (TransportPlan plan : transportPlanService.list(planQuery)) for (Map goods : parseArray(plan.getGoodsJson())) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add); + return dispatchedGoods(waybills, transportPlanService.list(planQuery)); + } + private List waybillsByMasterNo(String masterNo) { + if (Func.isEmpty(masterNo)) return List.of(); + return waybillService.list(new LambdaQueryWrapper().eq(Waybill::getMasterNo, masterNo)); + } + private Map dispatchedGoods(List waybills, List plans) { + Map result = new LinkedHashMap<>(); + for (Waybill bill : waybills) for (Map goods : waybillGoods(bill)) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add); + for (TransportPlan plan : plans) for (Map goods : parseArray(plan.getGoodsJson())) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add); return result; } + private List> waybillGoods(Waybill waybill) { + List> goods = parseArray(waybill.getGoodsJson()); + if (!goods.isEmpty()) return goods; + Map fallback = new LinkedHashMap<>(); + fallback.put("cargoName", waybill.getCargoName()); fallback.put("cargoType", waybill.getCargoType()); fallback.put("quantity", waybill.getQuantity()); + return List.of(fallback); + } + private boolean belongsToRoute(Waybill waybill, Map route) { + return belongsToRoute(waybill.getRelationNo(), waybill.getTransportType(), waybill.getDepartureName(), waybill.getDepartureAddress(), waybill.getArrivalName(), waybill.getArrivalAddress(), route); + } + private boolean belongsToRoute(TransportPlan plan, Map route) { + return belongsToRoute(plan.getRelationNo(), plan.getTransportType(), plan.getDepartureName(), plan.getDepartureAddress(), plan.getArrivalName(), plan.getArrivalAddress(), route); + } + private boolean belongsToRoute(String relationNo, String transportType, String departureName, String departureAddress, String arrivalName, String arrivalAddress, Map route) { + if (Func.isNotEmpty(relationNo)) return Objects.equals(relationNo, string(route, "segmentNo")); + return sameTransportType(transportType, string(route, "transportType")) + && sameLocation(departureName, departureAddress, string(route, "departureName"), string(route, "departureAddress")) + && sameLocation(arrivalName, arrivalAddress, string(route, "arrivalName"), string(route, "arrivalAddress")); + } + private boolean sameLocation(String name, String address, String routeName, String routeAddress) { + return (Func.isNotEmpty(address) && Objects.equals(address, routeAddress)) || (Func.isNotEmpty(name) && Objects.equals(name, routeName)); + } + private boolean sameTransportType(String left, String right) { + if (Objects.equals(left, right)) return true; + return transportTypeName(left).equals(transportTypeName(right)); + } + private String transportTypeName(String value) { + String normalized = value == null ? "" : value.toLowerCase(); + return switch (normalized) { + case "road" -> "公路运输"; + case "railway" -> "铁路运输"; + case "river" -> "水路运输"; + case "air" -> "航空运输"; + default -> value == null ? "" : value; + }; + } private BigDecimal totalQuantity(List> goods) { return goods.stream().map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add); } private String buildFreightJson(BigDecimal quantity, BigDecimal freightTotal, Map dispatch) { Map freight = new LinkedHashMap<>(); @@ -373,7 +423,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl dispatch) { return String.join("\u0000", string(dispatch, "segmentNo", ""), string(dispatch, "carrierType", ""), string(dispatch, "carrierName", ""), string(dispatch, "driverName", ""), string(dispatch, "vehicleNo", "")); } + private String waybillGroupKey(Map dispatch) { return String.join("\u0000", string(dispatch, "segmentNo", ""), string(dispatch, "carrierType", ""), string(dispatch, "carrierName", ""), string(dispatch, "driverName", ""), string(dispatch, "driverPhone", ""), string(dispatch, "vehicleNo", ""), string(dispatch, "captainName", ""), string(dispatch, "containerNo", ""), string(dispatch, "cabinNo", "")); } private String joinGoodsField(List> dispatches, String field) { return dispatches.stream().map(item -> string(item, field, "")).filter(Func::isNotEmpty).distinct().reduce((left, right) -> left + "、" + right).orElse(""); } private BigDecimal decimal(Map values, String key) { try { return new BigDecimal(string(values, key, "0")); } catch (Exception exception) { return BigDecimal.ZERO; } } private BigDecimal nullableDecimal(Map values, String key) { String value = string(values, key); if (Func.isEmpty(value)) return null; try { return new BigDecimal(value); } catch (Exception exception) { return null; } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java index 0462940..5120c70 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java @@ -154,9 +154,13 @@ public class VoucherManageServiceImpl extends BaseServiceImpl waybills = listRelatedWaybills(voucher); + log.info("[凭证处理] 进度 10%:已查询关联运单 voucherId={}, waybillCount={}", voucherId, waybills.size()); Map waybillByPlate = new HashMap<>(); for (Waybill waybill : waybills) { - waybillPlateNumbers(waybill).forEach(plateNo -> waybillByPlate.putIfAbsent(plateNo, waybill)); + for (String plateNo : waybillPlateNumbers(waybill)) { + Waybill existing = waybillByPlate.putIfAbsent(plateNo, waybill); + if (existing != null && !Objects.equals(existing.getId(), waybill.getId())) { + log.warn("[凭证处理] 车牌对应多个关联运单 voucherId={}, plateNo={}, firstWaybillNo={}, duplicateWaybillNo={}", + voucherId, plateNo, existing.getWaybillNo(), waybill.getWaybillNo()); + } + } } + log.info("[凭证处理] 进度 20%:已建立车牌匹配索引 voucherId={}, plateCount={}", voucherId, waybillByPlate.size()); voucherImageMapper.deleteByVoucherId(voucher.getId()); int imageCount = 0; + int matchedImageCount = 0; Set relatedWaybillIds = new HashSet<>(); try (InputStream source = openSourceFile(voucher.getFileUrl()); ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(source), StandardCharsets.UTF_8)) { @@ -214,7 +235,11 @@ public class VoucherManageServiceImpl extends BaseServiceImpl