From 317437ddba97eb8c13cd10cb719aa508b873f48f Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Tue, 7 Apr 2026 09:03:47 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=8E=A5=E5=8F=A3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../logistics/controller/PrintController.java | 90 +++++++++++++++++++ .../domain/entity/PrintMachineEntity.java | 45 ++++++++++ .../domain/entity/PrintRecordEntity.java | 44 +++++++++ .../scheduler/SampleOrderScannerJob.java | 20 ++--- .../service/base/PrintMachineService.java | 8 ++ .../service/base/PrintRecordService.java | 9 ++ .../base/impl/PrintMachineServiceImpl.java | 37 ++++++++ .../base/impl/PrintRecordServiceImpl.java | 21 +++++ .../handler/SfCreateOrderHandler.java | 39 ++++---- .../logistics/mapper/PrintMachineMapper.java | 9 ++ .../logistics/mapper/PrintRecordMapper.java | 9 ++ src/main/resources/application.yml | 2 +- 12 files changed, 304 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/project/logistics/controller/PrintController.java create mode 100644 src/main/java/com/project/logistics/domain/entity/PrintMachineEntity.java create mode 100644 src/main/java/com/project/logistics/domain/entity/PrintRecordEntity.java create mode 100644 src/main/java/com/project/logistics/domain/service/base/PrintMachineService.java create mode 100644 src/main/java/com/project/logistics/domain/service/base/PrintRecordService.java create mode 100644 src/main/java/com/project/logistics/domain/service/base/impl/PrintMachineServiceImpl.java create mode 100644 src/main/java/com/project/logistics/domain/service/base/impl/PrintRecordServiceImpl.java create mode 100644 src/main/java/com/project/logistics/mapper/PrintMachineMapper.java create mode 100644 src/main/java/com/project/logistics/mapper/PrintRecordMapper.java diff --git a/src/main/java/com/project/logistics/controller/PrintController.java b/src/main/java/com/project/logistics/controller/PrintController.java new file mode 100644 index 0000000..01936b4 --- /dev/null +++ b/src/main/java/com/project/logistics/controller/PrintController.java @@ -0,0 +1,90 @@ +package com.project.logistics.controller; + +import com.project.logistics.domain.entity.PrintRecordEntity; +import com.project.logistics.domain.service.base.PrintMachineService; +import com.project.logistics.domain.service.base.PrintRecordService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api/logistics/print") +public class PrintController { + + @Autowired + private PrintRecordService printRecordService; + + + @Autowired + private PrintMachineService printMachineService; + /** + * 1. 询问接口:我这台机器是否打过这张单? + * @return "true" 代表没打过,允许打印;"false" 代表已打过。 + */ + @GetMapping("/check") + public String check(@RequestParam("orderNo") String orderNo, + @RequestParam("machineId") String machineId) { + + // 只查询打印记录表 + boolean alreadyPrinted = printRecordService.lambdaQuery() + .eq(PrintRecordEntity::getOrderNo, orderNo) + .eq(PrintRecordEntity::getMachineId, machineId) + .eq(PrintRecordEntity::getPrintStatus, 1) + .count() > 0; + + if (alreadyPrinted) { + return "false"; + } + + log.info(">>> 机器 {} 请求打印新单据: {}", machineId, orderNo); + return "true"; + } + + @PostMapping("/heartbeat") + public Map heartbeat(@RequestBody Map params) { + String machineId = (String) params.get("machineId"); + String status = (String) params.get("status"); + + // 更新机器状态和时间 + printMachineService.updateHeartbeat(machineId, status); + + return Map.of("code", 200, "msg", "pong"); + } + + + /** + * 2. 反馈接口:打印成功,请记录。 + */ + @PostMapping("/callback") + public Map callback(@RequestBody Map params) { + String orderNo = (String) params.get("orderNo"); + String machineId = (String) params.get("machineId"); + Integer printStatus = (Integer) params.get("printStatus"); + + // 记录或更新打印日志 + PrintRecordEntity record = printRecordService.lambdaQuery() + .eq(PrintRecordEntity::getOrderNo, orderNo) + .eq(PrintRecordEntity::getMachineId, machineId) + .one(); + + if (record == null) { + record = new PrintRecordEntity(); + record.setOrderNo(orderNo); + record.setMachineId(machineId); + } + + record.setPrintStatus(printStatus); + printRecordService.saveOrUpdate(record); + + if (printStatus == 1) { + log.info(">>> 单据 {} 物理打印成功 (机器: {})", orderNo, machineId); + } else { + log.warn(">>> 单据 {} 物理打印失败 (机器: {})", orderNo, machineId); + } + + return Map.of("code", 200, "msg", "received"); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/entity/PrintMachineEntity.java b/src/main/java/com/project/logistics/domain/entity/PrintMachineEntity.java new file mode 100644 index 0000000..88b21cf --- /dev/null +++ b/src/main/java/com/project/logistics/domain/entity/PrintMachineEntity.java @@ -0,0 +1,45 @@ +package com.project.logistics.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.project.base.domain.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Comment; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = "auto_print_machine", indexes = { + @Index(name = "idx_machine_id", columnList = "machine_id", unique = true) +}) +@TableName("auto_print_machine") +@Comment("打印机在线状态表") +public class PrintMachineEntity extends BaseEntity { + + @Id + @Column(name = "id", columnDefinition = "bigint(20) comment '主键ID'") + private Long id; + + @Comment("机器标识 (MAC地址或PC名)") + @Column(name = "machine_id", length = 100, nullable = false) + @TableField("machine_id") + private String machineId; + + @Comment("最后心跳时间") + @Column(name = "last_heartbeat_time") + @TableField("last_heartbeat_time") + private LocalDateTime lastHeartbeatTime; + + @Comment("在线状态 (ONLINE/OFFLINE)") + @Column(name = "status", length = 20) + @TableField("status") + private String status; + + @Comment("客户端版本号") + @Column(name = "client_version", length = 20) + @TableField("client_version") + private String clientVersion; +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/entity/PrintRecordEntity.java b/src/main/java/com/project/logistics/domain/entity/PrintRecordEntity.java new file mode 100644 index 0000000..f87814c --- /dev/null +++ b/src/main/java/com/project/logistics/domain/entity/PrintRecordEntity.java @@ -0,0 +1,44 @@ +package com.project.logistics.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.project.base.domain.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Comment; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = "auto_print_record", indexes = { + @Index(name = "idx_print_order_no", columnList = "order_no", unique = true) +}) +@TableName("auto_print_record") +@Comment("面单物理打印记录表") +public class PrintRecordEntity extends BaseEntity { + + @Id + @Column(name = "id", columnDefinition = "bigint(20) comment '主键ID'") + private Long id; + + @Comment("业务单号 (U9单号)") + @Column(name = "order_no", length = 64, nullable = false) + @TableField("order_no") + private String orderNo; + + @Comment("执行打印的机器标识 (MAC地址或PC名)") + @Column(name = "machine_id", length = 100) + @TableField("machine_id") + private String machineId; + + @Comment("打印时的文件MD5 (校验用)") + @Column(name = "file_md5", length = 64) + @TableField("file_md5") + private String fileMd5; + + @Comment("打印状态 (1:成功)") + @Column(name = "status") + @TableField("status") + private Integer printStatus; +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/scheduler/SampleOrderScannerJob.java b/src/main/java/com/project/logistics/domain/scheduler/SampleOrderScannerJob.java index aaf6905..bc1427e 100644 --- a/src/main/java/com/project/logistics/domain/scheduler/SampleOrderScannerJob.java +++ b/src/main/java/com/project/logistics/domain/scheduler/SampleOrderScannerJob.java @@ -131,16 +131,16 @@ public class SampleOrderScannerJob { return false; } - // 获取今天的日期字符串 (格式: yyyy-MM-dd) - String today = cn.hutool.core.date.DateUtil.today(); - // 截取 U9 时间字符串的前 10 位进行比较 (防止 U9 返回的是 yyyy-MM-dd HH:mm:ss) - String actualSendDate = actualSendTimeStr.trim().substring(0, 10); - - if (!today.equals(actualSendDate)) { - log.info("样品单号 {} 实际发货时间 [{}] 不是当天 [{}], 不执行自动下单", - orderNo, actualSendDate, today); - return false; - } +// // 获取今天的日期字符串 (格式: yyyy-MM-dd) +// String today = cn.hutool.core.date.DateUtil.today(); +// // 截取 U9 时间字符串的前 10 位进行比较 (防止 U9 返回的是 yyyy-MM-dd HH:mm:ss) +// String actualSendDate = actualSendTimeStr.trim().substring(0, 10); +// +// if (!today.equals(actualSendDate)) { +// log.info("样品单号 {} 实际发货时间 [{}] 不是当天 [{}], 不执行自动下单", +// orderNo, actualSendDate, today); +// return false; +// } // 持久化订单:跳过上传资源步骤,直接设置为 RESOURCE_CREATED LogisticsOrderEntity order = new LogisticsOrderEntity(); diff --git a/src/main/java/com/project/logistics/domain/service/base/PrintMachineService.java b/src/main/java/com/project/logistics/domain/service/base/PrintMachineService.java new file mode 100644 index 0000000..5da1613 --- /dev/null +++ b/src/main/java/com/project/logistics/domain/service/base/PrintMachineService.java @@ -0,0 +1,8 @@ +package com.project.logistics.domain.service.base; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.project.logistics.domain.entity.PrintMachineEntity; + +public interface PrintMachineService extends IService { + void updateHeartbeat(String machineId, String status); +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/service/base/PrintRecordService.java b/src/main/java/com/project/logistics/domain/service/base/PrintRecordService.java new file mode 100644 index 0000000..547ab92 --- /dev/null +++ b/src/main/java/com/project/logistics/domain/service/base/PrintRecordService.java @@ -0,0 +1,9 @@ +package com.project.logistics.domain.service.base; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.project.logistics.domain.entity.PrintRecordEntity; + +public interface PrintRecordService extends IService { + + boolean isPrintedByThisMachine(String orderNo, String machineId); +} diff --git a/src/main/java/com/project/logistics/domain/service/base/impl/PrintMachineServiceImpl.java b/src/main/java/com/project/logistics/domain/service/base/impl/PrintMachineServiceImpl.java new file mode 100644 index 0000000..082d999 --- /dev/null +++ b/src/main/java/com/project/logistics/domain/service/base/impl/PrintMachineServiceImpl.java @@ -0,0 +1,37 @@ +package com.project.logistics.domain.service.base.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.project.logistics.domain.entity.PrintMachineEntity; +import com.project.logistics.domain.service.base.PrintMachineService; +import com.project.logistics.mapper.PrintMachineMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@Slf4j +public class PrintMachineServiceImpl extends ServiceImpl + implements PrintMachineService { + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateHeartbeat(String machineId, String status) { + // 使用 MyBatis-Plus 的 LambdaQuery 检查是否存在记录 + PrintMachineEntity machine = this.lambdaQuery() + .eq(PrintMachineEntity::getMachineId, machineId) + .one(); + + if (machine == null) { + machine = new PrintMachineEntity(); + machine.setMachineId(machineId); + } + + machine.setStatus(status); + machine.setLastHeartbeatTime(LocalDateTime.now()); + + // 保存或更新 + this.saveOrUpdate(machine); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/service/base/impl/PrintRecordServiceImpl.java b/src/main/java/com/project/logistics/domain/service/base/impl/PrintRecordServiceImpl.java new file mode 100644 index 0000000..90f235d --- /dev/null +++ b/src/main/java/com/project/logistics/domain/service/base/impl/PrintRecordServiceImpl.java @@ -0,0 +1,21 @@ +package com.project.logistics.domain.service.base.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.project.logistics.domain.entity.PrintRecordEntity; +import com.project.logistics.domain.service.base.PrintRecordService; +import com.project.logistics.mapper.PrintRecordMapper; +import org.springframework.stereotype.Service; + + +@Service +public class PrintRecordServiceImpl extends ServiceImpl + implements PrintRecordService { + + @Override + public boolean isPrintedByThisMachine(String orderNo, String machineId) { + return this.lambdaQuery() + .eq(PrintRecordEntity::getOrderNo, orderNo) + .eq(PrintRecordEntity::getMachineId, machineId) + .eq(PrintRecordEntity::getPrintStatus, 1) + .count() > 0; } +} diff --git a/src/main/java/com/project/logistics/domain/strategy/handler/SfCreateOrderHandler.java b/src/main/java/com/project/logistics/domain/strategy/handler/SfCreateOrderHandler.java index 39fe209..38be0c7 100644 --- a/src/main/java/com/project/logistics/domain/strategy/handler/SfCreateOrderHandler.java +++ b/src/main/java/com/project/logistics/domain/strategy/handler/SfCreateOrderHandler.java @@ -76,32 +76,39 @@ public class SfCreateOrderHandler implements ApiTaskHandler { String transportMethod = u9Data.getString(ShipmentOrderInfoFieldEnum.transportMethod.name()); if (StrUtil.isBlank(transportMethod) || !transportMethod.contains("顺丰")) { - // 需要终止任务,匹配字段失败,失败原因:运输方式及货场名称不为顺丰 - terminateBusinessTask(task, order, "匹配字段失败:运输方式及货场名称不为顺丰 (" + transportMethod + ")"); - return; + throw new RuntimeException("校验不通过,等待重试:运输方式及货场名称不为顺丰 (" + transportMethod + ")"); } String payer = u9Data.getString(ShipmentOrderInfoFieldEnum.payer.name()); SfPayMethodEnum senderPayByU9Desc = SfPayMethodEnum.getSenderPayByU9Desc(payer); if (Objects.isNull(senderPayByU9Desc)) { // 需要终止任务,匹配字段失败,失败原因:运费承担字段匹配失败 - terminateBusinessTask(task, order, "匹配字段失败:运费承担字段 (" + payer + ") 无法匹配顺丰付款方式"); - return; + throw new RuntimeException("校验不通过,等待重试:运费承担字段 (" + payer + ") 无法匹配顺丰付款方式"); } String expressType = u9Data.getString(ShipmentOrderInfoFieldEnum.expressType.name()); SfExpressTypeEnum sfStandardExpressByCode = SfExpressTypeEnum.getSfStandardExpressByCode(expressType); if (Objects.isNull(sfStandardExpressByCode)) { - // 需要终止任务,匹配字段失败,失败原因:未匹配快件产品类别 - terminateBusinessTask(task, order, "匹配字段失败:单据产品类别 (" + expressType + ") 未匹配到顺丰业务类型"); - return; + throw new RuntimeException("校验不通过,等待重试:单据产品类别 (" + expressType + ") 未匹配到顺丰业务类型"); } if (StrUtil.equals(order.getOrderType() , OrderTypeEnum.SHIPMENT.name()) && StrUtil.isBlank(order.getResourceCode())) { - terminateBusinessTask(task, order, "常规出货单缺少资源编码"); - return; + throw new RuntimeException("校验不通过,等待重试:常规出货单缺少资源编码"); } + + String recipientContact = u9Data.getString(ShipmentOrderInfoFieldEnum.recipientContact.name()); + if (StrUtil.isBlank(recipientContact)) { + throw new RuntimeException("校验不通过,等待重试:收货人不能为空"); + } + + String sendContact = SfPayMethodEnum.SENDER_PAY.equals(senderPayByU9Desc) ? + u9Data.getString(ShipmentOrderInfoFieldEnum.salesman.name()) : + fixedRuleProperties.getSenderPayContact(); + if (StrUtil.isBlank(sendContact)) { + throw new RuntimeException("校验不通过,等待重试:寄件人不能为空"); + } + msgData.put("cargoDesc", "交换机"); JSONArray cargoDetails = new JSONArray(); JSONObject item = new JSONObject(); @@ -116,9 +123,7 @@ public class SfCreateOrderHandler implements ApiTaskHandler { JSONObject sender = new JSONObject(); sender.put("contactType", SfContactTypeEnum.SENDER.getCode()); // 寄付取业务的名字,到付取固定 - sender.put("contact", SfPayMethodEnum.SENDER_PAY.equals(senderPayByU9Desc) ? - u9Data.getString(ShipmentOrderInfoFieldEnum.salesman.name()) : - fixedRuleProperties.getSenderPayContact()); + sender.put("contact", sendContact); sender.put("mobile", fixedRuleProperties.getSenderMobile()); sender.put("address", fixedRuleProperties.getSenderAddress()); contactInfoList.add(sender); @@ -126,7 +131,7 @@ public class SfCreateOrderHandler implements ApiTaskHandler { // 收件方 (从 U9 快照中动态获取) JSONObject receiver = new JSONObject(); receiver.put("contactType", SfContactTypeEnum.RECIPIENT.getCode()); - receiver.put("contact", u9Data.getString(ShipmentOrderInfoFieldEnum.recipientContact.name())); + receiver.put("contact", recipientContact); receiver.put("mobile", u9Data.getString(ShipmentOrderInfoFieldEnum.recipientMobile.name())); receiver.put("address", u9Data.getString(ShipmentOrderInfoFieldEnum.recipientAddress.name())); contactInfoList.add(receiver); @@ -217,10 +222,8 @@ public class SfCreateOrderHandler implements ApiTaskHandler { logisticsOrderService.updateById(order); // 2. 将当前任务设为取消/死信状态,防止定时任务继续重试 - task.setTaskStatus(TaskStatusEnum.CANCELLED.getCode()); + task.setTaskStatus(TaskStatusEnum.FAILED.getCode()); task.setErrorMessage(reason); - // 注意:任务表的最终 update 会由 ApiRetryJob 在 handle 执行完后统一处理 - // 此处抛出一个特定异常或正常返回即可。由于 ApiRetryJob 会根据异常重试, - // 我们这里选择不抛异常,让 ApiRetryJob 认为执行“结束”了(但状态已改)。 + } } \ No newline at end of file diff --git a/src/main/java/com/project/logistics/mapper/PrintMachineMapper.java b/src/main/java/com/project/logistics/mapper/PrintMachineMapper.java new file mode 100644 index 0000000..55ff3f1 --- /dev/null +++ b/src/main/java/com/project/logistics/mapper/PrintMachineMapper.java @@ -0,0 +1,9 @@ +package com.project.logistics.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.logistics.domain.entity.PrintMachineEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PrintMachineMapper extends BaseMapper { +} diff --git a/src/main/java/com/project/logistics/mapper/PrintRecordMapper.java b/src/main/java/com/project/logistics/mapper/PrintRecordMapper.java new file mode 100644 index 0000000..ddb33da --- /dev/null +++ b/src/main/java/com/project/logistics/mapper/PrintRecordMapper.java @@ -0,0 +1,9 @@ +package com.project.logistics.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.logistics.domain.entity.PrintRecordEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PrintRecordMapper extends BaseMapper { +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a8f8208..4884923 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -115,4 +115,4 @@ fixed-rule: senderAddress: '广东省广州市番禺区石碁镇南荔东路56号' monthlyCard: '7551234567' scheduled-task: - owner: test \ No newline at end of file + owner: local \ No newline at end of file