diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 23abb4d..f79b0cf 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/main/java/com/project/logistics/config/WebDavProperties.java b/src/main/java/com/project/logistics/config/WebDavProperties.java index b4dd11c..b16bbff 100644 --- a/src/main/java/com/project/logistics/config/WebDavProperties.java +++ b/src/main/java/com/project/logistics/config/WebDavProperties.java @@ -42,7 +42,7 @@ public class WebDavProperties { * 签收回单(POD)的存放根路径 * 例如: /U9_Orders/Signed_Receipts */ - private String podRoot; + private String podRoot = "/电子签收单"; /** * 连接超时时间 (毫秒),默认 10000 diff --git a/src/main/java/com/project/logistics/domain/enums/ShipmentOrderInfoFieldEnum.java b/src/main/java/com/project/logistics/domain/enums/ShipmentOrderInfoFieldEnum.java index 43a084b..64fb533 100644 --- a/src/main/java/com/project/logistics/domain/enums/ShipmentOrderInfoFieldEnum.java +++ b/src/main/java/com/project/logistics/domain/enums/ShipmentOrderInfoFieldEnum.java @@ -19,6 +19,7 @@ public enum ShipmentOrderInfoFieldEnum { expressType("DescFlexField_PrivateDescSeg20","寄付方式") , fee("DescFlexField_PrivateDescSeg3" , "运费") , resourceCode("DescFlexField_PrivateDescSeg18" , "资源编码"), + quantity("SM_Ship.DescFlexField_PrivateDescSeg2" , "件数") , ; private final String fieldName; diff --git a/src/main/java/com/project/logistics/domain/scheduler/ApiRetryJob.java b/src/main/java/com/project/logistics/domain/scheduler/ApiRetryJob.java index 2d5d108..7aacc7d 100644 --- a/src/main/java/com/project/logistics/domain/scheduler/ApiRetryJob.java +++ b/src/main/java/com/project/logistics/domain/scheduler/ApiRetryJob.java @@ -1,5 +1,7 @@ package com.project.logistics.domain.scheduler; +import cn.hutool.core.util.StrUtil; +import com.project.base.config.ScheduledTaskProperties; import com.project.logistics.domain.entity.ApiRetryTaskEntity; import com.project.logistics.domain.entity.LogisticsOrderEntity; import com.project.logistics.domain.enums.TaskStatusEnum; @@ -29,6 +31,9 @@ public class ApiRetryJob { // Spring 会自动把所有实现了 ApiTaskHandler 的类注入到这个 List 里 private final Map handlerMap; + @Autowired + private ScheduledTaskProperties scheduledTaskProperties; + @Autowired public ApiRetryJob(List handlers) { // 将 List 转换成 Map,方便通过 ActionCode 快速查找对应的处理器 @@ -42,6 +47,10 @@ public class ApiRetryJob { */ @Scheduled(fixedDelay = 10000) public void executePendingTasks() throws Exception { + if (StrUtil.equals(scheduledTaskProperties.getOwner() , "local")) { + log.error("跳过ApiRetryJob定时任务"); + return; + } // 1. 从数据库捞取所有 状态=PENDING/FAILED 且 执行时间<=当前时间 的任务 List pendingTasks = apiRetryTaskService.listPendingTasks(); diff --git a/src/main/java/com/project/logistics/domain/scheduler/WebDavDirectoryInitJob.java b/src/main/java/com/project/logistics/domain/scheduler/WebDavDirectoryInitJob.java new file mode 100644 index 0000000..0e923d8 --- /dev/null +++ b/src/main/java/com/project/logistics/domain/scheduler/WebDavDirectoryInitJob.java @@ -0,0 +1,55 @@ +package com.project.logistics.domain.scheduler; + +import com.project.logistics.config.WebDavProperties; +import com.project.logistics.domain.service.WebDavService; +import com.project.logistics.domain.utils.FilePathUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Slf4j +@Component +public class WebDavDirectoryInitJob { + + @Autowired + private WebDavService webDavService; + + @Autowired + private WebDavProperties webDavProperties; + + /** + * 每天凌晨 00:00:01 执行 + * 预先创建好当天所有的业务文件夹 + */ + @Scheduled(cron = "8 0 0 * * ?") + public void initTodayDirectories() { + log.info(">>> [系统预热] 开始初始化今日 WebDAV 业务目录..."); + + // 1. 生成今日层级路径: 2026年/2026年3月/2026年3月30日 + String datePath = FilePathUtil.getHierarchicalPath(new Date()); + + try { + // 2. 初始化【待处理出货单】目录 (用户上班要往里放文件) + webDavService.initFolder(webDavProperties.getShipmentRoot(), datePath); + + // 3. 初始化【已自动下单出货单】目录 (下单成功后文件会自动移入) + webDavService.initFolder(webDavProperties.getProcessedFolderName(), datePath); + + // 4. 初始化【顺丰面单】目录 + // 假设你在 WebDavProperties 中定义了 waybillRoot + webDavService.initFolder("顺丰面单", datePath); + + // 5. 初始化【签收回单】目录 + webDavService.initFolder(webDavProperties.getPodRoot(), datePath); + + log.info(">>> [系统预热] 今日所有目录初始化完成!"); + + } catch (Exception e) { + log.error(">>> [系统预热] 自动创建今日目录失败: {}", e.getMessage()); + // 注意:这里失败没关系,业务运行时的 ensureDirectoryExists 还有一次兜底机会 + } + } +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/service/WebDavService.java b/src/main/java/com/project/logistics/domain/service/WebDavService.java index efd7fe5..1396e90 100644 --- a/src/main/java/com/project/logistics/domain/service/WebDavService.java +++ b/src/main/java/com/project/logistics/domain/service/WebDavService.java @@ -145,4 +145,22 @@ public class WebDavService { String normalized = rawUrl.replaceAll("(?>> 正在初始化业务目录: {}", rootName + "/" + datePath); + // 调用之前实现的递归创建逻辑 + ensureDirectoryExists(sardine, encodedUrl); + } } \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/service/base/ErpService.java b/src/main/java/com/project/logistics/domain/service/base/ErpService.java index 5631bba..ebb99fa 100644 --- a/src/main/java/com/project/logistics/domain/service/base/ErpService.java +++ b/src/main/java/com/project/logistics/domain/service/base/ErpService.java @@ -2,6 +2,8 @@ package com.project.logistics.domain.service.base; import cn.hutool.json.JSONObject; +import java.math.BigDecimal; + public interface ErpService { @@ -11,4 +13,8 @@ public interface ErpService { * 回写运单号到 U9 */ void updateWaybillToErp(String orderNo, String waybillNo , String resourceCode) throws Exception; + + void updateStatusToErp(String orderNo, String statusDesc) throws Exception; + + void updateFeeToErp(String orderNo, Integer qty, BigDecimal amount) throws Exception; } diff --git a/src/main/java/com/project/logistics/domain/service/base/ApiRetryTaskServiceImpl.java b/src/main/java/com/project/logistics/domain/service/base/impl/ApiRetryTaskServiceImpl.java similarity index 95% rename from src/main/java/com/project/logistics/domain/service/base/ApiRetryTaskServiceImpl.java rename to src/main/java/com/project/logistics/domain/service/base/impl/ApiRetryTaskServiceImpl.java index 95a801c..84d6991 100644 --- a/src/main/java/com/project/logistics/domain/service/base/ApiRetryTaskServiceImpl.java +++ b/src/main/java/com/project/logistics/domain/service/base/impl/ApiRetryTaskServiceImpl.java @@ -1,9 +1,10 @@ -package com.project.logistics.domain.service.base; +package com.project.logistics.domain.service.base.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.project.logistics.domain.entity.ApiRetryTaskEntity; import com.project.logistics.domain.enums.RetryActionEnum; import com.project.logistics.domain.enums.TaskStatusEnum; +import com.project.logistics.domain.service.base.ApiRetryTaskService; import com.project.logistics.mapper.ApiRetryTaskMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/project/logistics/domain/service/base/ErpServiceImpl.java b/src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java similarity index 73% rename from src/main/java/com/project/logistics/domain/service/base/ErpServiceImpl.java rename to src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java index 6d52b3a..12bb7bc 100644 --- a/src/main/java/com/project/logistics/domain/service/base/ErpServiceImpl.java +++ b/src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java @@ -1,13 +1,15 @@ -package com.project.logistics.domain.service.base; +package com.project.logistics.domain.service.base.impl; import cn.hutool.json.JSONObject; import com.project.logistics.domain.enums.ShipmentOrderInfoFieldEnum; +import com.project.logistics.domain.service.base.ErpService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.Map; @Service @@ -70,4 +72,35 @@ public class ErpServiceImpl implements ErpService { throw e; // 抛出异常,触发 ApiRetryJob 的重试机制 } } + + /** + * 通用的状态回写方法 (揽收、签收) + */ + @Override + public void updateStatusToErp(String orderNo, String statusDesc) throws Exception { + log.info(">>> [U9回写状态] 单号: {}, 目标状态: {}", orderNo, statusDesc); + + // 假设 U9 的状态字段是 StatusDesc,或者是自定义字段 DescFlexField_Pub_2 + String sql = "UPDATE SM_Ship SET DescFlexField_Pub_2 = ? WHERE DocNo = ?"; + + int rows = u9JdbcTemplate.update(sql, statusDesc, orderNo); + if (rows == 0) { + throw new RuntimeException("U9单据不存在,回写状态失败"); + } + } + + @Override + public void updateFeeToErp(String orderNo, Integer qty, BigDecimal amount) throws Exception { + // 财务安全:确保回写的金额 100% 是两位小数 + BigDecimal formattedAmount = amount.setScale(2, java.math.RoundingMode.HALF_UP); + + log.info(">>> [U9回写费用] 单号: {}, 最终金额: {}", orderNo, formattedAmount); + + String sql = String.format("UPDATE SM_Ship SET %s = ?, %s = ? WHERE DocNo = ?" , + ShipmentOrderInfoFieldEnum.quantity.getFieldName() , + ShipmentOrderInfoFieldEnum.fee.getFieldName()); + + // 执行更新 + u9JdbcTemplate.update(sql, qty, formattedAmount, orderNo); + } } diff --git a/src/main/java/com/project/logistics/domain/service/base/LogisticsOrderServiceImpl.java b/src/main/java/com/project/logistics/domain/service/base/impl/LogisticsOrderServiceImpl.java similarity index 93% rename from src/main/java/com/project/logistics/domain/service/base/LogisticsOrderServiceImpl.java rename to src/main/java/com/project/logistics/domain/service/base/impl/LogisticsOrderServiceImpl.java index e1e3dfc..b7fb27e 100644 --- a/src/main/java/com/project/logistics/domain/service/base/LogisticsOrderServiceImpl.java +++ b/src/main/java/com/project/logistics/domain/service/base/impl/LogisticsOrderServiceImpl.java @@ -1,4 +1,4 @@ -package com.project.logistics.domain.service.base; +package com.project.logistics.domain.service.base.impl; import cn.hutool.json.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -8,6 +8,8 @@ import com.project.logistics.domain.enums.OrderStatusEnum; import com.project.logistics.domain.enums.OrderTypeEnum; import com.project.logistics.domain.enums.RetryActionEnum; import com.project.logistics.domain.enums.TaskStatusEnum; +import com.project.logistics.domain.service.base.ApiRetryTaskService; +import com.project.logistics.domain.service.base.LogisticsOrderService; import com.project.logistics.mapper.LogisticsOrderMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; 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 a18aa69..6abf71a 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 @@ -79,6 +79,12 @@ public class SfCreateOrderHandler implements ApiTaskHandler { terminateBusinessTask(task, order, "匹配字段失败:单据产品类别 (" + expressType + ") 未匹配到顺丰业务类型"); return; } + + if (StrUtil.equals(order.getOrderType() , OrderTypeEnum.SHIPMENT.name()) && + StrUtil.isBlank(order.getResourceCode())) { + terminateBusinessTask(task, order, "常规出货单缺少资源编码"); + return; + } msgData.put("cargoDesc", "交换机"); JSONArray cargoDetails = new JSONArray(); JSONObject item = new JSONObject(); @@ -102,7 +108,7 @@ public class SfCreateOrderHandler implements ApiTaskHandler { // 收件方 (从 U9 快照中动态获取) JSONObject receiver = new JSONObject(); - receiver.put("contactType", SfContactTypeEnum.SENDER.getCode()); + receiver.put("contactType", SfContactTypeEnum.RECIPIENT.getCode()); receiver.put("contact", u9Data.getString(ShipmentOrderInfoFieldEnum.recipientContact.name())); // 假设 U9 字段名是这个 receiver.put("mobile", u9Data.getString(ShipmentOrderInfoFieldEnum.recipientMobile.name())); receiver.put("address", u9Data.getString(ShipmentOrderInfoFieldEnum.recipientAddress.name())); diff --git a/src/main/java/com/project/receive/controller/ReceiveController.java b/src/main/java/com/project/receive/controller/ReceiveController.java index 87fcae6..92f2204 100644 --- a/src/main/java/com/project/receive/controller/ReceiveController.java +++ b/src/main/java/com/project/receive/controller/ReceiveController.java @@ -1,162 +1,87 @@ package com.project.receive.controller; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.project.receive.dto.*; -import com.project.receive.utils.SfDecryptUtil; + +import com.project.receive.domain.dto.SfRoutePushRequest; +import com.project.receive.domain.service.ReceiveService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import java.io.FileOutputStream; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; -@RestController @Slf4j -@RequestMapping("/api/") +@RestController +@RequestMapping("/api/sf/callback") public class ReceiveController { - private static final String SF_CHECKWORD = "ZXmoWOQdSd2UTBmSP6Kv3VW9Q4N5dJqz"; + @Autowired + private ReceiveService receiveService; + /** - * 接收顺丰订单状态推送接口 - * 对应文档 2.6 节 JSON 示例 + * 1. 路由状态回调 (揽收/签收) + * 对应接口: PushOrderState */ - @PostMapping("/pushOrderState") - public SfPushResponse receiveOrderState(@RequestBody SfPushRequest request) { - // 1. 打印接收到的原始数据日志 - log.info("==== 收到顺丰状态推送 ===="); - log.info("Request ID: {}", request.getRequestId()); - log.info("Timestamp: {}", request.getTimestamp()); + @PostMapping("/route") + public Map handleRoutePush(@RequestBody SfRoutePushRequest request) { + log.info(">>> 收到顺丰路由推送,包含节点数: {}", + (request.getBody() != null && request.getBody().getWaybillRoute() != null) ? + request.getBody().getWaybillRoute().size() : 0); - if (request.getOrderState() != null) { - request.getOrderState().forEach(state -> { - log.info("订单号: {}, 运单号: {}, 状态码: {}, 描述: {}", - state.getOrderNo(), - state.getWaybillNo(), - state.getOrderStateCode(), - state.getOrderStateDesc()); - }); - } + try { + // 业务处理逻辑 + receiveService.processRoutePush(request); - // 2. 根据文档 2.7 节,返回成功响应 - return SfPushResponse.ok(); + // 返回顺丰要求的成功格式 + return successResponse(); + } catch (Exception e) { + log.error(">>> 路由解析处理失败", e); + return failResponse(e.getMessage()); + } } - - /** - * 接收顺丰路由推送 - * 对应文档 2.5 节 JSON 示例 + * 2. 费用与重量回调 + * 对应接口: EXP_RECE_WAYBILLS_FEE_PUSH */ - @PostMapping("/pushRoute") - public SfRouteResponse receiveRoute(@RequestBody SfRoutePushRequest request) { - log.info(">>>> 收到顺丰路由信息推送 <<<<"); - - if (request.getBody() != null && request.getBody().getWaybillRoute() != null) { - for (WaybillRoute route : request.getBody().getWaybillRoute()) { - log.info("运单号: {}, 订单号: {}, 时间: {}, 状态: {}, 备注: {}", - route.getMailno(), - route.getOrderid(), - route.getAcceptTime(), - route.getOpCode(), - route.getRemark()); - } - } else { - log.warn("收到的路由数据内容为空"); + @PostMapping("/fee") + public Map handleFeePush(@RequestParam("content") String content) { + log.info(">>> 收到顺丰【运费重量】推送: {}", content); + try { + receiveService.saveFeeLog(content); + return successResponse(); + } catch (Exception e) { + log.error("运费处理异常", e); + return failResponse(e.getMessage()); } - - // 根据文档 2.6,必须返回 0000 告知顺丰接收成功,否则顺丰会重复推送 - return SfRouteResponse.ok(); } - - @RequestMapping("/pushOrderStateRaw") - public String receiveRaw(@RequestBody String rawBody) { - log.info("收到原始报文: {}", rawBody); - // 返回 JSON 格式的成功字符串 - return "{\"success\":\"true\",\"code\":\"0\",\"msg\":\"\"}"; - } - - @Autowired - private ObjectMapper objectMapper; - /** - * 接收顺丰运费推送 - * 报文类型: application/x-www-form-urlencoded + * 3. 电子回单图片回调 + * 对应接口: 图片注册及推送接口 */ - @PostMapping(value = "/pushFee", consumes = "application/x-www-form-urlencoded") - public SfFeeResponse receiveFee( - @RequestParam("content") String content, - @RequestParam(value = "sign", required = false) String sign) { - - log.info(">>>> 收到顺丰运费推送 <<<<"); - log.info("签名(sign): {}", sign); - log.info("原始内容(content): {}", content); - + @PostMapping("/pod-picture") + public Map handlePodPicturePush(@RequestParam("content") String content) { + log.info(">>> 收到顺丰【电子回单图片】推送 (内容较长,不完整打印)"); try { - // 将 content 字符串解析为 Java 对象 - SfFeeContent feeData = objectMapper.readValue(content, SfFeeContent.class); - - log.info("解析成功 - 运单号: {}, 订单号: {}, 计费重量: {}", - feeData.getWaybillNo(), - feeData.getOrderNo(), - feeData.getMeterageWeightQty()); - - if (feeData.getFeeList() != null) { - feeData.getFeeList().forEach(fee -> { - log.info("费用项 - 类型: {}, 金额: {}", fee.getFeeTypeCode(), fee.getFeeAmt()); - }); - } - - // 返回成功响应 (code 必须为 200) - return SfFeeResponse.ok("your_partner_code"); - + receiveService.savePodPictureLog(content); + return successResponse(); } catch (Exception e) { - log.error("解析顺丰运费推送失败", e); - SfFeeResponse error = new SfFeeResponse(); - error.setCode(400); - error.setMessage("解析异常"); - return error; + log.error("回单图片处理异常", e); + return failResponse(e.getMessage()); } } - @PostMapping("/pushElectronicReceipt") - public SfPicturePushResponse receiveReceipt(@RequestBody SfPicturePushRequest request) { - log.info(">>>> 收到顺丰电子回单(IN149)推送, 运单号: {} <<<<", request.getWaybillNo()); - - try { - // 步骤 1:解密最外层的 content 得到内部 JSON 字符串 - byte[] firstLevelDecrypted = SfDecryptUtil.decrypt(request.getContent(), SF_CHECKWORD); - String innerJsonStr = new String(firstLevelDecrypted, "UTF-8"); - log.info("第一层解密成功"); - - // 步骤 2:解析内部 JSON 获取文件密文 - SfInnerContent innerContent = objectMapper.readValue(innerJsonStr, SfInnerContent.class); - String encryptedFileBase64 = innerContent.getContent(); - - // 步骤 3:对文件密文进行第二层 AES 解密 (根据文档示例,文件内容也是加密的) - // 先 Base64 解码,再 AES 解密 - byte[] pdfBytes = SfDecryptUtil.decrypt(encryptedFileBase64, SF_CHECKWORD); - log.info("第二层解密(文件流)成功,大小: {} bytes", pdfBytes.length); - - // 步骤 4:保存为 PDF 文件到当前目录 - String fileName = request.getWaybillNo() + "_receipt.pdf"; - Path path = Paths.get(System.getProperty("user.dir"), fileName); - - try (FileOutputStream fos = new FileOutputStream(path.toFile())) { - fos.write(pdfBytes); - fos.flush(); - } - - log.info("电子回单已保存至: {}", path.toAbsolutePath()); - - return SfPicturePushResponse.ok(); + private Map successResponse() { + Map res = new HashMap<>(); + res.put("success", true); + res.put("errorCode", "S0000"); + return res; + } - } catch (Exception e) { - log.error("处理电子回单推送失败", e); - SfPicturePushResponse error = new SfPicturePushResponse(); - error.setReturnCode("1000"); - error.setReturnMsg("解析保存失败: " + e.getMessage()); - return error; - } + private Map failResponse(String msg) { + Map res = new HashMap<>(); + res.put("success", false); + res.put("errorMsg", msg); + return res; } -} +} \ No newline at end of file diff --git a/src/main/java/com/project/receive/domain/dto/SfRoutePushRequest.java b/src/main/java/com/project/receive/domain/dto/SfRoutePushRequest.java new file mode 100644 index 0000000..047b9e2 --- /dev/null +++ b/src/main/java/com/project/receive/domain/dto/SfRoutePushRequest.java @@ -0,0 +1,29 @@ +package com.project.receive.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; + +@Data +public class SfRoutePushRequest { + + @JsonProperty("Body") // 必须对应顺丰的大写 Body + private RouteBody body; + + @Data + public static class RouteBody { + @JsonProperty("WaybillRoute") // 必须对应顺丰的大写 WaybillRoute + private List waybillRoute; + } + + @Data + public static class WaybillRouteDetail { + private String mailno; // 顺丰运单号 + private String orderid; // 客户订单号 (U9单号) + private String acceptAddress; // 发生地址 + private String acceptTime; // 发生时间 + private String remark; // 备注内容 + private String opCode; // 操作码 (50:揽收, 80:签收) + private String id; // 推送ID + } +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/entity/FeePushLogEntity.java b/src/main/java/com/project/receive/domain/entity/FeePushLogEntity.java similarity index 91% rename from src/main/java/com/project/logistics/domain/entity/FeePushLogEntity.java rename to src/main/java/com/project/receive/domain/entity/FeePushLogEntity.java index d446c29..bd75948 100644 --- a/src/main/java/com/project/logistics/domain/entity/FeePushLogEntity.java +++ b/src/main/java/com/project/receive/domain/entity/FeePushLogEntity.java @@ -1,4 +1,4 @@ -package com.project.logistics.domain.entity; +package com.project.receive.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -30,6 +30,11 @@ public class FeePushLogEntity extends BaseEntity { @TableField("order_no") private String orderNo; + @Comment("件数") + @Column(name = "item_count") + @TableField("item_count") + private Integer itemCount; + @Comment("关联顺丰运单号") @Column(name = "waybill_no", length = 64, nullable = false) @TableField("waybill_no") diff --git a/src/main/java/com/project/logistics/domain/entity/PodPushLogEntity.java b/src/main/java/com/project/receive/domain/entity/PodPushLogEntity.java similarity index 97% rename from src/main/java/com/project/logistics/domain/entity/PodPushLogEntity.java rename to src/main/java/com/project/receive/domain/entity/PodPushLogEntity.java index 562d578..8dbbb3c 100644 --- a/src/main/java/com/project/logistics/domain/entity/PodPushLogEntity.java +++ b/src/main/java/com/project/receive/domain/entity/PodPushLogEntity.java @@ -1,4 +1,4 @@ -package com.project.logistics.domain.entity; +package com.project.receive.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; diff --git a/src/main/java/com/project/logistics/domain/entity/RoutePushLogEntity.java b/src/main/java/com/project/receive/domain/entity/RoutePushLogEntity.java similarity index 78% rename from src/main/java/com/project/logistics/domain/entity/RoutePushLogEntity.java rename to src/main/java/com/project/receive/domain/entity/RoutePushLogEntity.java index 81ec32e..7b4aeac 100644 --- a/src/main/java/com/project/logistics/domain/entity/RoutePushLogEntity.java +++ b/src/main/java/com/project/receive/domain/entity/RoutePushLogEntity.java @@ -1,4 +1,4 @@ -package com.project.logistics.domain.entity; +package com.project.receive.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -33,15 +33,20 @@ public class RoutePushLogEntity extends BaseEntity { @TableField("order_no") private String orderNo; - @Comment("顺丰路由状态代码") - @Column(name = "order_state_code", length = 32) - @TableField("order_state_code") - private String orderStateCode; + @Comment("操作时间") + @Column(name = "accept_time", length = 200) + @TableField("accept_time") + private String acceptTime; + + @Comment("路由操作码 (顺丰opCode, 京东status等)") + @Column(name = "op_code", length = 32) + @TableField("op_code") + private String opCode; @Comment("顺丰路由状态描述(如:已收取、派件中、签收成功等)") - @Column(name = "order_state_desc", length = 255) - @TableField("order_state_desc") - private String orderStateDesc; + @Column(name = "remark", length = 2000) + @TableField("remark") + private String remark; @Comment("收派员工号") @Column(name = "emp_code", length = 100) diff --git a/src/main/java/com/project/receive/domain/service/ReceiveService.java b/src/main/java/com/project/receive/domain/service/ReceiveService.java new file mode 100644 index 0000000..e0bfde7 --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/ReceiveService.java @@ -0,0 +1,181 @@ +package com.project.receive.domain.service; + +import com.jayway.jsonpath.JsonPath; +import com.project.logistics.domain.entity.LogisticsOrderEntity; + +import com.project.logistics.domain.enums.*; +import com.project.logistics.domain.service.base.*; +import com.project.receive.domain.dto.SfRoutePushRequest; +import com.project.receive.domain.entity.FeePushLogEntity; +import com.project.receive.domain.entity.PodPushLogEntity; +import com.project.receive.domain.entity.RoutePushLogEntity; +import com.project.receive.domain.service.base.FeePushLogService; +import com.project.receive.domain.service.base.PodPushLogService; +import com.project.receive.domain.service.base.RoutePushLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; + +@Slf4j +@Service +public class ReceiveService { + + @Autowired + private LogisticsOrderService logisticsOrderService; + @Autowired + private ApiRetryTaskService apiRetryTaskService; + + @Autowired + private RoutePushLogService routePushLogService; + @Autowired + private FeePushLogService feePushLogService; + @Autowired + private PodPushLogService podPushLogService; + + /** + * 处理路由推送:识别揽收与签收 + */ + public void processRoutePush(SfRoutePushRequest request) throws Exception { + if (request.getBody() == null || request.getBody().getWaybillRoute() == null) { + return; + } + + // 循环处理顺丰推过来的每一个路由节点 + for (SfRoutePushRequest.WaybillRouteDetail detail : request.getBody().getWaybillRoute()) { + + log.info(">>> 正在解析路由: 单号={}, 操作码={}, 描述={}", + detail.getOrderid(), detail.getOpCode(), detail.getRemark()); + + // 1. 存入流水日志 + saveToLogTable(detail); + + // 2. 状态机流转 (仅针对揽收50和签收80) + handleStatusMachine(detail); + } + } + + private void handleStatusMachine(SfRoutePushRequest.WaybillRouteDetail detail) throws Exception { + String orderNo = detail.getOrderid(); + String opCode = detail.getOpCode(); + + // 尝试获取本地订单 + LogisticsOrderEntity order = logisticsOrderService.getByOrderNo(orderNo); + if (order == null) { + log.warn(">>> 收到路由推送,但本地库找不到单号: {}", orderNo); + return; + } + + // 判断操作码 + if (SfRouteOpCodeEnum.PICKED_UP.getCode().equals(opCode)) { + // 已揽收 + order.setOrderStatus(OrderStatusEnum.PICKED_UP.getCode()); + logisticsOrderService.updateById(order); + // 触发回写 ERP 任务 + apiRetryTaskService.createNextTask(orderNo, RetryActionEnum.ERP_UPDATE_PICKED_UP); + } + else if (SfRouteOpCodeEnum.DELIVERED.getCode().equals(opCode)) { + // 已签收 + order.setOrderStatus(OrderStatusEnum.DELIVERED.getCode()); + logisticsOrderService.updateById(order); + // 触发回写 ERP 任务 + apiRetryTaskService.createNextTask(orderNo, RetryActionEnum.ERP_UPDATE_DELIVERED); + } + } + + private void saveToLogTable(SfRoutePushRequest.WaybillRouteDetail detail) { + RoutePushLogEntity logEntity = new RoutePushLogEntity(); + logEntity.setOrderNo(detail.getOrderid()); + logEntity.setWaybillNo(detail.getMailno()); + logEntity.setOpCode(detail.getOpCode()); + logEntity.setRemark(detail.getRemark()); + logEntity.setAcceptTime(detail.getAcceptTime()); + routePushLogService.save(logEntity); + } + /** + * 处理运费推送 + */ + @Transactional(rollbackFor = Exception.class) + public void saveFeeLog(String rawJson) { + try { + // 1. 提取基础信息 + String orderNo = JsonPath.read(rawJson, "$.orderNo"); + String waybillNo = JsonPath.read(rawJson, "$.waybillNo"); + + // 提取件数 (quantity) + Object qtyObj = JsonPath.read(rawJson, "$.quantity"); + Integer itemCount = new java.math.BigDecimal(String.valueOf(qtyObj)).intValue(); + + // 提取重量 (meterageWeightQty) + Object weightObj = JsonPath.read(rawJson, "$.meterageWeightQty"); + BigDecimal weight = new java.math.BigDecimal(String.valueOf(weightObj)) + .setScale(3, java.math.RoundingMode.HALF_UP); + // 2. 【核心】计算总费用 + // 从 feeList 数组中提取所有 feeAmt 并求和 + List fees = JsonPath.read(rawJson, "$.feeList[*].feeAmt"); + BigDecimal totalFee = BigDecimal.ZERO; + if (fees != null) { + for (Object f : fees) { + totalFee = totalFee.add(new BigDecimal(String.valueOf(f))); + } + } + totalFee = totalFee.setScale(2, java.math.RoundingMode.HALF_UP); + + log.info(">>> 解析运费报文: 单号={}, 件数={}, 重量={}, 总费={}", + orderNo, itemCount, weight, totalFee); + + // 3. 存入数据库 + FeePushLogEntity feeEntity = new FeePushLogEntity(); + feeEntity.setOrderNo(orderNo); + feeEntity.setWaybillNo(waybillNo); + feeEntity.setItemCount(itemCount); + feeEntity.setRealWeightQty(weight); + feeEntity.setTotalFeeAmt(totalFee); + feeEntity.setSyncStatus(SyncStatusEnum.WAIT.getCode()); + feeEntity.setRawPushData(rawJson); + feePushLogService.save(feeEntity); + + // 4. 驱动状态机与回写任务 + // 找到主订单,如果当前状态是已揽收,则推进到“运费已清点”状态 + LogisticsOrderEntity order = logisticsOrderService.getByOrderNo(orderNo); + if (order != null) { + // 只有状态处于 PICKED_UP 时才更新主表状态,避免覆盖更高级的 DELIVERED 状态 + if (OrderStatusEnum.PICKED_UP.getCode().equals(order.getOrderStatus())) { + order.setOrderStatus(OrderStatusEnum.ERP_FEE_UPDATED.getCode()); + logisticsOrderService.updateById(order); + } + + // 无论主表状态如何,都要触发一次“回写运费到ERP”的任务 + apiRetryTaskService.createNextTask(orderNo, RetryActionEnum.ERP_UPDATE_FEE); + } + + } catch (Exception e) { + log.error(">>> 运费报文解析失败: {}", rawJson, e); + throw new RuntimeException("FEE_PARSE_ERROR"); + } + } + /** + * 处理 POD 图片推送 + */ + public void savePodPictureLog(String content) throws Exception { + // 关键:POD 推送包含巨大的 Base64 字节,这里只做存库。 + // 具体的解析、下载、传 WebDAV 交由后续的 SF_DOWNLOAD_POD 任务去重试执行。 + String waybillNo = JsonPath.read(content, "$.waybillNo"); + + PodPushLogEntity podLog = new PodPushLogEntity(); + podLog.setWaybillNo(waybillNo); + podLog.setRawPushData(content); // 存入 LONGTEXT 字段 + podLog.setProcessStatus(SyncStatusEnum.WAIT.getCode()); + podPushLogService.save(podLog); + + // 查找订单号并触发“处理图片”的异步任务 + LogisticsOrderEntity order = logisticsOrderService.lambdaQuery() + .eq(LogisticsOrderEntity::getSfWaybillNo, waybillNo).one(); + if (order != null) { + apiRetryTaskService.createNextTask(order.getOrderNo(), RetryActionEnum.SF_DOWNLOAD_POD); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/project/receive/domain/service/base/FeePushLogService.java b/src/main/java/com/project/receive/domain/service/base/FeePushLogService.java new file mode 100644 index 0000000..d2c8eb1 --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/base/FeePushLogService.java @@ -0,0 +1,7 @@ +package com.project.receive.domain.service.base; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.project.receive.domain.entity.FeePushLogEntity; + +public interface FeePushLogService extends IService { +} diff --git a/src/main/java/com/project/receive/domain/service/base/PodPushLogService.java b/src/main/java/com/project/receive/domain/service/base/PodPushLogService.java new file mode 100644 index 0000000..2819880 --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/base/PodPushLogService.java @@ -0,0 +1,7 @@ +package com.project.receive.domain.service.base; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.project.receive.domain.entity.PodPushLogEntity; + +public interface PodPushLogService extends IService { +} diff --git a/src/main/java/com/project/receive/domain/service/base/RoutePushLogService.java b/src/main/java/com/project/receive/domain/service/base/RoutePushLogService.java new file mode 100644 index 0000000..7941daa --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/base/RoutePushLogService.java @@ -0,0 +1,7 @@ +package com.project.receive.domain.service.base; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.project.receive.domain.entity.RoutePushLogEntity; + +public interface RoutePushLogService extends IService { +} diff --git a/src/main/java/com/project/receive/domain/service/base/impl/FeePushLogServiceImpl.java b/src/main/java/com/project/receive/domain/service/base/impl/FeePushLogServiceImpl.java new file mode 100644 index 0000000..6cdb3f2 --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/base/impl/FeePushLogServiceImpl.java @@ -0,0 +1,12 @@ +package com.project.receive.domain.service.base.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.project.receive.domain.entity.FeePushLogEntity; +import com.project.receive.domain.service.base.FeePushLogService; +import com.project.receive.mapper.FeePushLogMapper; +import org.springframework.stereotype.Service; + +@Service +public class FeePushLogServiceImpl extends ServiceImpl + implements FeePushLogService { +} diff --git a/src/main/java/com/project/receive/domain/service/base/impl/PodPushLogServiceImpl.java b/src/main/java/com/project/receive/domain/service/base/impl/PodPushLogServiceImpl.java new file mode 100644 index 0000000..21b7f67 --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/base/impl/PodPushLogServiceImpl.java @@ -0,0 +1,13 @@ +package com.project.receive.domain.service.base.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.project.receive.domain.entity.PodPushLogEntity; +import com.project.receive.domain.service.base.PodPushLogService; +import com.project.receive.mapper.PodPushLogMapper; +import org.springframework.stereotype.Service; + +@Service +public class PodPushLogServiceImpl extends ServiceImpl + implements PodPushLogService { +} diff --git a/src/main/java/com/project/receive/domain/service/base/impl/RoutePushLogServiceImpl.java b/src/main/java/com/project/receive/domain/service/base/impl/RoutePushLogServiceImpl.java new file mode 100644 index 0000000..b28008d --- /dev/null +++ b/src/main/java/com/project/receive/domain/service/base/impl/RoutePushLogServiceImpl.java @@ -0,0 +1,13 @@ +package com.project.receive.domain.service.base.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.project.receive.domain.entity.RoutePushLogEntity; +import com.project.receive.domain.service.base.RoutePushLogService; +import com.project.receive.mapper.RoutePushLogMapper; +import org.springframework.stereotype.Service; + + +@Service +public class RoutePushLogServiceImpl extends ServiceImpl + implements RoutePushLogService { +} diff --git a/src/main/java/com/project/receive/mapper/FeePushLogMapper.java b/src/main/java/com/project/receive/mapper/FeePushLogMapper.java new file mode 100644 index 0000000..7ceae64 --- /dev/null +++ b/src/main/java/com/project/receive/mapper/FeePushLogMapper.java @@ -0,0 +1,10 @@ +package com.project.receive.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.receive.domain.entity.FeePushLogEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper + +public interface FeePushLogMapper extends BaseMapper { +} diff --git a/src/main/java/com/project/receive/mapper/PodPushLogMapper.java b/src/main/java/com/project/receive/mapper/PodPushLogMapper.java new file mode 100644 index 0000000..8bdb591 --- /dev/null +++ b/src/main/java/com/project/receive/mapper/PodPushLogMapper.java @@ -0,0 +1,10 @@ +package com.project.receive.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.receive.domain.entity.PodPushLogEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper + +public interface PodPushLogMapper extends BaseMapper { +} diff --git a/src/main/java/com/project/receive/mapper/RoutePushLogMapper.java b/src/main/java/com/project/receive/mapper/RoutePushLogMapper.java new file mode 100644 index 0000000..c2ac84c --- /dev/null +++ b/src/main/java/com/project/receive/mapper/RoutePushLogMapper.java @@ -0,0 +1,10 @@ +package com.project.receive.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.receive.domain.entity.RoutePushLogEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper + +public interface RoutePushLogMapper extends BaseMapper { +} diff --git a/src/main/java/com/project/receive/strategy/ErpUpdateFeeHandler.java b/src/main/java/com/project/receive/strategy/ErpUpdateFeeHandler.java new file mode 100644 index 0000000..f75b1cf --- /dev/null +++ b/src/main/java/com/project/receive/strategy/ErpUpdateFeeHandler.java @@ -0,0 +1,58 @@ +package com.project.receive.strategy; + +import com.project.logistics.domain.entity.ApiRetryTaskEntity; +import com.project.logistics.domain.entity.LogisticsOrderEntity; +import com.project.logistics.domain.enums.RetryActionEnum; +import com.project.logistics.domain.enums.SyncStatusEnum; +import com.project.logistics.domain.service.base.ErpService; +import com.project.logistics.domain.strategy.ApiTaskHandler; +import com.project.receive.domain.entity.FeePushLogEntity; +import com.project.receive.domain.service.base.FeePushLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ErpUpdateFeeHandler implements ApiTaskHandler { + + @Autowired + private ErpService erpService; + + @Autowired + private FeePushLogService feePushLogService; + + @Override + public String getActionCode() { + return RetryActionEnum.ERP_UPDATE_FEE.getCode(); + } + + @Override + public void handle(ApiRetryTaskEntity task, LogisticsOrderEntity order) throws Exception { + log.info(">>> 开始回写运费件数到 ERP,单号: {}", order.getOrderNo()); + + // 1. 获取顺丰推过来的费用明细 (取最新的一条) + FeePushLogEntity feeLog = feePushLogService.lambdaQuery() + .eq(FeePushLogEntity::getOrderNo, order.getOrderNo()) + .orderByDesc(FeePushLogEntity::getCreateTime) + .last("limit 1") + .one(); + + if (feeLog == null) { + throw new RuntimeException("尚未收到顺丰的计费推送报文,任务等待重试"); + } + + // 2. 调用 U9 专线执行更新 + erpService.updateFeeToErp( + order.getOrderNo(), + feeLog.getItemCount(), + feeLog.getTotalFeeAmt() + ); + + // 3. 更新费用日志的同步状态 + feeLog.setSyncStatus(SyncStatusEnum.SUCCESS.getCode()); + feePushLogService.updateById(feeLog); + + log.info(">>> ERP 费用回写成功,单号: {}", order.getOrderNo()); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/receivedemo/controller/ReceiveDemoController.java b/src/main/java/com/project/receivedemo/controller/ReceiveDemoController.java new file mode 100644 index 0000000..3356d33 --- /dev/null +++ b/src/main/java/com/project/receivedemo/controller/ReceiveDemoController.java @@ -0,0 +1,162 @@ +package com.project.receivedemo.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.project.receivedemo.dto.*; +import com.project.receivedemo.utils.SfDecryptUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.FileOutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; + +@RestController +@Slf4j +@RequestMapping("/api/") +public class ReceiveDemoController { + + private static final String SF_CHECKWORD = "ZXmoWOQdSd2UTBmSP6Kv3VW9Q4N5dJqz"; + /** + * 接收顺丰订单状态推送接口 + * 对应文档 2.6 节 JSON 示例 + */ + @PostMapping("/pushOrderState") + public SfPushResponse receiveOrderState(@RequestBody SfPushRequest request) { + // 1. 打印接收到的原始数据日志 + log.info("==== 收到顺丰状态推送 ===="); + log.info("Request ID: {}", request.getRequestId()); + log.info("Timestamp: {}", request.getTimestamp()); + + if (request.getOrderState() != null) { + request.getOrderState().forEach(state -> { + log.info("订单号: {}, 运单号: {}, 状态码: {}, 描述: {}", + state.getOrderNo(), + state.getWaybillNo(), + state.getOrderStateCode(), + state.getOrderStateDesc()); + }); + } + + // 2. 根据文档 2.7 节,返回成功响应 + return SfPushResponse.ok(); + } + + + /** + * 接收顺丰路由推送 + * 对应文档 2.5 节 JSON 示例 + */ + @PostMapping("/pushRoute") + public SfRouteResponse receiveRoute(@RequestBody SfRoutePushRequest request) { + log.info(">>>> 收到顺丰路由信息推送 <<<<"); + + if (request.getBody() != null && request.getBody().getWaybillRoute() != null) { + for (WaybillRoute route : request.getBody().getWaybillRoute()) { + log.info("运单号: {}, 订单号: {}, 时间: {}, 状态: {}, 备注: {}", + route.getMailno(), + route.getOrderid(), + route.getAcceptTime(), + route.getOpCode(), + route.getRemark()); + } + } else { + log.warn("收到的路由数据内容为空"); + } + + // 根据文档 2.6,必须返回 0000 告知顺丰接收成功,否则顺丰会重复推送 + return SfRouteResponse.ok(); + } + + + @RequestMapping("/pushOrderStateRaw") + public String receiveRaw(@RequestBody String rawBody) { + log.info("收到原始报文: {}", rawBody); + // 返回 JSON 格式的成功字符串 + return "{\"success\":\"true\",\"code\":\"0\",\"msg\":\"\"}"; + } + + @Autowired + private ObjectMapper objectMapper; + + /** + * 接收顺丰运费推送 + * 报文类型: application/x-www-form-urlencoded + */ + @PostMapping(value = "/pushFee", consumes = "application/x-www-form-urlencoded") + public SfFeeResponse receiveFee( + @RequestParam("content") String content, + @RequestParam(value = "sign", required = false) String sign) { + + log.info(">>>> 收到顺丰运费推送 <<<<"); + log.info("签名(sign): {}", sign); + log.info("原始内容(content): {}", content); + + try { + // 将 content 字符串解析为 Java 对象 + SfFeeContent feeData = objectMapper.readValue(content, SfFeeContent.class); + + log.info("解析成功 - 运单号: {}, 订单号: {}, 计费重量: {}", + feeData.getWaybillNo(), + feeData.getOrderNo(), + feeData.getMeterageWeightQty()); + + if (feeData.getFeeList() != null) { + feeData.getFeeList().forEach(fee -> { + log.info("费用项 - 类型: {}, 金额: {}", fee.getFeeTypeCode(), fee.getFeeAmt()); + }); + } + + // 返回成功响应 (code 必须为 200) + return SfFeeResponse.ok("your_partner_code"); + + } catch (Exception e) { + log.error("解析顺丰运费推送失败", e); + SfFeeResponse error = new SfFeeResponse(); + error.setCode(400); + error.setMessage("解析异常"); + return error; + } + } + + @PostMapping("/pushElectronicReceipt") + public SfPicturePushResponse receiveReceipt(@RequestBody SfPicturePushRequest request) { + log.info(">>>> 收到顺丰电子回单(IN149)推送, 运单号: {} <<<<", request.getWaybillNo()); + + try { + // 步骤 1:解密最外层的 content 得到内部 JSON 字符串 + byte[] firstLevelDecrypted = SfDecryptUtil.decrypt(request.getContent(), SF_CHECKWORD); + String innerJsonStr = new String(firstLevelDecrypted, "UTF-8"); + log.info("第一层解密成功"); + + // 步骤 2:解析内部 JSON 获取文件密文 + SfInnerContent innerContent = objectMapper.readValue(innerJsonStr, SfInnerContent.class); + String encryptedFileBase64 = innerContent.getContent(); + + // 步骤 3:对文件密文进行第二层 AES 解密 (根据文档示例,文件内容也是加密的) + // 先 Base64 解码,再 AES 解密 + byte[] pdfBytes = SfDecryptUtil.decrypt(encryptedFileBase64, SF_CHECKWORD); + log.info("第二层解密(文件流)成功,大小: {} bytes", pdfBytes.length); + + // 步骤 4:保存为 PDF 文件到当前目录 + String fileName = request.getWaybillNo() + "_receipt.pdf"; + Path path = Paths.get(System.getProperty("user.dir"), fileName); + + try (FileOutputStream fos = new FileOutputStream(path.toFile())) { + fos.write(pdfBytes); + fos.flush(); + } + + log.info("电子回单已保存至: {}", path.toAbsolutePath()); + + return SfPicturePushResponse.ok(); + + } catch (Exception e) { + log.error("处理电子回单推送失败", e); + SfPicturePushResponse error = new SfPicturePushResponse(); + error.setReturnCode("1000"); + error.setReturnMsg("解析保存失败: " + e.getMessage()); + return error; + } + } +} diff --git a/src/main/java/com/project/receive/dto/FeeInfo.java b/src/main/java/com/project/receivedemo/dto/FeeInfo.java similarity index 91% rename from src/main/java/com/project/receive/dto/FeeInfo.java rename to src/main/java/com/project/receivedemo/dto/FeeInfo.java index c4cfe17..2cdd572 100644 --- a/src/main/java/com/project/receive/dto/FeeInfo.java +++ b/src/main/java/com/project/receivedemo/dto/FeeInfo.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/OrderStateDetail.java b/src/main/java/com/project/receivedemo/dto/OrderStateDetail.java similarity index 91% rename from src/main/java/com/project/receive/dto/OrderStateDetail.java rename to src/main/java/com/project/receivedemo/dto/OrderStateDetail.java index 5eb2f58..603be13 100644 --- a/src/main/java/com/project/receive/dto/OrderStateDetail.java +++ b/src/main/java/com/project/receivedemo/dto/OrderStateDetail.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/RouteBody.java b/src/main/java/com/project/receivedemo/dto/RouteBody.java similarity index 85% rename from src/main/java/com/project/receive/dto/RouteBody.java rename to src/main/java/com/project/receivedemo/dto/RouteBody.java index bdd4f1d..265eeef 100644 --- a/src/main/java/com/project/receive/dto/RouteBody.java +++ b/src/main/java/com/project/receivedemo/dto/RouteBody.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfFeeContent.java b/src/main/java/com/project/receivedemo/dto/SfFeeContent.java similarity index 94% rename from src/main/java/com/project/receive/dto/SfFeeContent.java rename to src/main/java/com/project/receivedemo/dto/SfFeeContent.java index 6faf1b6..b845b4f 100644 --- a/src/main/java/com/project/receive/dto/SfFeeContent.java +++ b/src/main/java/com/project/receivedemo/dto/SfFeeContent.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfFeeResponse.java b/src/main/java/com/project/receivedemo/dto/SfFeeResponse.java similarity index 92% rename from src/main/java/com/project/receive/dto/SfFeeResponse.java rename to src/main/java/com/project/receivedemo/dto/SfFeeResponse.java index be3dfa9..45bb335 100644 --- a/src/main/java/com/project/receive/dto/SfFeeResponse.java +++ b/src/main/java/com/project/receivedemo/dto/SfFeeResponse.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfInnerContent.java b/src/main/java/com/project/receivedemo/dto/SfInnerContent.java similarity index 84% rename from src/main/java/com/project/receive/dto/SfInnerContent.java rename to src/main/java/com/project/receivedemo/dto/SfInnerContent.java index 2d49eeb..e2c2b08 100644 --- a/src/main/java/com/project/receive/dto/SfInnerContent.java +++ b/src/main/java/com/project/receivedemo/dto/SfInnerContent.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfPicturePushRequest.java b/src/main/java/com/project/receivedemo/dto/SfPicturePushRequest.java similarity index 90% rename from src/main/java/com/project/receive/dto/SfPicturePushRequest.java rename to src/main/java/com/project/receivedemo/dto/SfPicturePushRequest.java index 5e9267d..dcef394 100644 --- a/src/main/java/com/project/receive/dto/SfPicturePushRequest.java +++ b/src/main/java/com/project/receivedemo/dto/SfPicturePushRequest.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfPicturePushResponse.java b/src/main/java/com/project/receivedemo/dto/SfPicturePushResponse.java similarity index 95% rename from src/main/java/com/project/receive/dto/SfPicturePushResponse.java rename to src/main/java/com/project/receivedemo/dto/SfPicturePushResponse.java index eacd394..aaa939c 100644 --- a/src/main/java/com/project/receive/dto/SfPicturePushResponse.java +++ b/src/main/java/com/project/receivedemo/dto/SfPicturePushResponse.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfPushRequest.java b/src/main/java/com/project/receivedemo/dto/SfPushRequest.java similarity index 86% rename from src/main/java/com/project/receive/dto/SfPushRequest.java rename to src/main/java/com/project/receivedemo/dto/SfPushRequest.java index e4c857e..eeaaf08 100644 --- a/src/main/java/com/project/receive/dto/SfPushRequest.java +++ b/src/main/java/com/project/receivedemo/dto/SfPushRequest.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfPushResponse.java b/src/main/java/com/project/receivedemo/dto/SfPushResponse.java similarity index 90% rename from src/main/java/com/project/receive/dto/SfPushResponse.java rename to src/main/java/com/project/receivedemo/dto/SfPushResponse.java index f8f2801..9867c7d 100644 --- a/src/main/java/com/project/receive/dto/SfPushResponse.java +++ b/src/main/java/com/project/receivedemo/dto/SfPushResponse.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfRoutePushRequest.java b/src/main/java/com/project/receivedemo/dto/SfRoutePushRequest.java similarity index 82% rename from src/main/java/com/project/receive/dto/SfRoutePushRequest.java rename to src/main/java/com/project/receivedemo/dto/SfRoutePushRequest.java index fb91a07..67fdd11 100644 --- a/src/main/java/com/project/receive/dto/SfRoutePushRequest.java +++ b/src/main/java/com/project/receivedemo/dto/SfRoutePushRequest.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/SfRouteResponse.java b/src/main/java/com/project/receivedemo/dto/SfRouteResponse.java similarity index 92% rename from src/main/java/com/project/receive/dto/SfRouteResponse.java rename to src/main/java/com/project/receivedemo/dto/SfRouteResponse.java index 72ae18a..4b21143 100644 --- a/src/main/java/com/project/receive/dto/SfRouteResponse.java +++ b/src/main/java/com/project/receivedemo/dto/SfRouteResponse.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/src/main/java/com/project/receive/dto/WaybillRoute.java b/src/main/java/com/project/receivedemo/dto/WaybillRoute.java similarity index 95% rename from src/main/java/com/project/receive/dto/WaybillRoute.java rename to src/main/java/com/project/receivedemo/dto/WaybillRoute.java index bf98fa6..6301376 100644 --- a/src/main/java/com/project/receive/dto/WaybillRoute.java +++ b/src/main/java/com/project/receivedemo/dto/WaybillRoute.java @@ -1,4 +1,4 @@ -package com.project.receive.dto; +package com.project.receivedemo.dto; import lombok.Data; diff --git a/src/main/java/com/project/receive/utils/SfDecryptUtil.java b/src/main/java/com/project/receivedemo/utils/SfDecryptUtil.java similarity index 95% rename from src/main/java/com/project/receive/utils/SfDecryptUtil.java rename to src/main/java/com/project/receivedemo/utils/SfDecryptUtil.java index 98b6cb0..06aa183 100644 --- a/src/main/java/com/project/receive/utils/SfDecryptUtil.java +++ b/src/main/java/com/project/receivedemo/utils/SfDecryptUtil.java @@ -1,4 +1,4 @@ -package com.project.receive.utils; +package com.project.receivedemo.utils; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 330df37..3c66a8c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -97,4 +97,6 @@ fixed-rule: senderPayContact: '王盛荣' senderMobile: '13480155048' senderAddress: '广东省广州市番禺区石碁镇南荔东路56号' - monthlyCard: '7551234567' \ No newline at end of file + monthlyCard: '7551234567' +scheduled-task: + owner: local \ No newline at end of file