diff --git a/src/main/java/com/project/logistics/config/LogisticsSampleScannerProperties.java b/src/main/java/com/project/logistics/config/LogisticsSampleScannerProperties.java index e4a22f1..a0f7e7e 100644 --- a/src/main/java/com/project/logistics/config/LogisticsSampleScannerProperties.java +++ b/src/main/java/com/project/logistics/config/LogisticsSampleScannerProperties.java @@ -13,7 +13,7 @@ import java.util.List; public class LogisticsSampleScannerProperties { private boolean enabled = true; - private String goLiveTime = "2026-04-01"; + private String goLiveTime = "2026-05-19"; /** * 默认每 10 分钟唤醒一次检查(10分钟唤醒一次开销极低,且能满足20分钟及以上的间隔) */ diff --git a/src/main/java/com/project/logistics/controller/ApiRetryTaskController.java b/src/main/java/com/project/logistics/controller/ApiRetryTaskController.java new file mode 100644 index 0000000..1479a79 --- /dev/null +++ b/src/main/java/com/project/logistics/controller/ApiRetryTaskController.java @@ -0,0 +1,127 @@ +package com.project.logistics.controller; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.project.logistics.domain.entity.ApiRetryTaskEntity; +import com.project.logistics.domain.entity.LogisticsOrderEntity; +import com.project.logistics.domain.service.base.ApiRetryTaskService; +import com.project.logistics.domain.service.base.LogisticsOrderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@RestController +@RequestMapping("/api/task") +public class ApiRetryTaskController { + + @Autowired + private ApiRetryTaskService apiRetryTaskService; + + @Autowired + private LogisticsOrderService logisticsOrderService; + + @GetMapping("/{orderNo}") + public Map getTaskDetail(@PathVariable String orderNo) throws Exception { + // 1. 查订单信息 + LogisticsOrderEntity order = logisticsOrderService.getByOrderNo(orderNo); + if (order == null) { + return Map.of("code", 404, "msg", "订单不存在: " + orderNo); + } + + // 2. 查该订单所有任务(按创建时间正序,组成时间线) + List tasks = apiRetryTaskService.lambdaQuery() + .eq(ApiRetryTaskEntity::getOrderNo, orderNo) + .orderByAsc(ApiRetryTaskEntity::getCreateTime) + .list(); + + // 3. 构时间线 + List> timeline = tasks.stream().map(task -> { + Map item = new LinkedHashMap<>(); + item.put("actionCode", task.getActionCode()); + item.put("status", task.getTaskStatus()); + item.put("retryCount", task.getRetryCount() + "/" + task.getMaxRetries()); + item.put("createTime", formatTime(task.getCreateTime())); + item.put("executeTime", formatTime(task.getUpdateTime())); + item.put("nextExecuteTime", formatTime(task.getNextExecuteTime())); + item.put("errorMessage", task.getErrorMessage()); + + // 请求/响应报文太长的只显示前1000字符 + if (StrUtil.isNotBlank(task.getRequestData())) { + String req = task.getRequestData(); + item.put("requestData", req.length() > 1000 ? req.substring(0, 1000) + "..." : req); + } + if (StrUtil.isNotBlank(task.getResponseData())) { + String resp = task.getResponseData(); + item.put("responseData", resp.length() > 1000 ? resp.substring(0, 1000) + "..." : resp); + } + return item; + }).toList(); + + // 4. 汇总 + Map orderInfo = new LinkedHashMap<>(); + orderInfo.put("orderNo", order.getOrderNo()); + orderInfo.put("orderType", order.getOrderType()); + orderInfo.put("orderStatus", order.getOrderStatus()); + orderInfo.put("sfWaybillNo", order.getSfWaybillNo()); + orderInfo.put("resourceCode", order.getResourceCode()); + orderInfo.put("createTime", formatTime(order.getCreateTime())); + orderInfo.put("orderInfo", StrUtil.isNotBlank(order.getOrderInfo()) ? + (order.getOrderInfo().length() > 500 ? order.getOrderInfo().substring(0, 500) + "..." : order.getOrderInfo()) : null); + + return Map.of( + "code", 200, + "order", orderInfo, + "tasks", timeline, + "taskCount", tasks.size() + ); + } + + /** + * 可选:根据actionCode查看所有失败的任务(用于全局排查) + */ + @GetMapping("/failed") + public Map listFailedTasks( + @RequestParam(required = false) String actionCode, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int size) { + + var query = apiRetryTaskService.lambdaQuery() + .in(ApiRetryTaskEntity::getTaskStatus, List.of("FAILED", "MAX_RETRY_FAILED")); + + + if (StrUtil.isNotBlank(actionCode)) { + query.eq(ApiRetryTaskEntity::getActionCode, actionCode); + } + + long total = query.count(); + List tasks = query + .orderByDesc(ApiRetryTaskEntity::getUpdateTime).list(); + + List> list = tasks.stream().map(task -> { + Map item = new LinkedHashMap<>(); + item.put("orderNo", task.getOrderNo()); + item.put("actionCode", task.getActionCode()); + item.put("status", task.getTaskStatus()); + item.put("retryCount", task.getRetryCount()); + item.put("errorMessage", task.getErrorMessage()); + item.put("updateTime", formatTime(task.getUpdateTime())); + return item; + }).toList(); + // + return Map.of( + "code", 200, + "data", list, + "total", total, + "page", page, + "size", size + ); + } + + private String formatTime(Date date) { + return date == null ? null : DateUtil.format(date, "yyyy-MM-dd HH:mm:ss"); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/logistics/domain/enums/SampleOrderInfoFieldEnum.java b/src/main/java/com/project/logistics/domain/enums/SampleOrderInfoFieldEnum.java index f22a1b3..1523599 100644 --- a/src/main/java/com/project/logistics/domain/enums/SampleOrderInfoFieldEnum.java +++ b/src/main/java/com/project/logistics/domain/enums/SampleOrderInfoFieldEnum.java @@ -16,9 +16,11 @@ public enum SampleOrderInfoFieldEnum { recipientAddress("DescFlexField_PubDescSeg14" , "收货地址") , payer("DescFlexField_PubDescSeg20" , "运费承担") , waybillNo("DescFlexField_PrivateDescSeg20" , "货运单号") , - expressType("DescFlexField_PrivateDescSeg25" , "寄付方式") , + expressType("DescFlexField_PrivateDescSeg26" , "寄付方式") , fee("DescFlexField_PrivateDescSeg21" , "运费") , quantity("DescFlexField_PrivateDescSeg13" , "件数") , + freightPhone("DescFlexField_PrivateDescSeg23" , "货场电话") , + createdOn("CreatedOn" , "创建人") , ; 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 3217bd8..64bdaa8 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,10 @@ public enum ShipmentOrderInfoFieldEnum { fee("DescFlexField_PrivateDescSeg3" , "运费") , resourceCode("DescFlexField_PrivateDescSeg18" , "资源编码"), quantity("DescFlexField_PrivateDescSeg2" , "件数") , + + freightPhone("DescFlexField_PrivateDescSeg13" , "货场电话"), + createdOn("CreatedOn" , "创建人") , + ; 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 7aacc7d..af4fd75 100644 --- a/src/main/java/com/project/logistics/domain/scheduler/ApiRetryJob.java +++ b/src/main/java/com/project/logistics/domain/scheduler/ApiRetryJob.java @@ -116,7 +116,7 @@ public class ApiRetryJob { task.setRetryCount(currentRetry); task.setErrorMessage(e.getMessage()); - if (currentRetry >= task.getMaxRetries()) { + if (currentRetry > task.getMaxRetries()) { // 超过最大重试次数,标记为死信,需人工排查 task.setTaskStatus(TaskStatusEnum.MAX_RETRY_FAILED.getCode()); log.error("!!!任务已达最大重试次数,彻底失败,需人工介入!!!"); 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 bc1427e..887d381 100644 --- a/src/main/java/com/project/logistics/domain/scheduler/SampleOrderScannerJob.java +++ b/src/main/java/com/project/logistics/domain/scheduler/SampleOrderScannerJob.java @@ -92,6 +92,10 @@ public class SampleOrderScannerJob { for (JSONObject u9Data : sampleOrders) { String orderNo = u9Data.getStr("orderNo"); +// if (!StrUtil.equalsAny(orderNo , "YP-GD11-20260518-015" , "YP-S2913-20260519-010")) { +// log.error("样品单 [{}] 跳过处理", orderNo); +// continue; +// } try { if (processSampleImport(orderNo, u9Data)) { successCount++; diff --git a/src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java b/src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java index 8e5fe35..83a5d85 100644 --- a/src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java +++ b/src/main/java/com/project/logistics/domain/service/base/impl/ErpServiceImpl.java @@ -44,7 +44,9 @@ public class ErpServiceImpl implements ErpService { String.format("%s as %s," , ShipmentOrderInfoFieldEnum.recipientAddress.getFieldName() , ShipmentOrderInfoFieldEnum.recipientAddress.name()) + String.format("%s as %s," , ShipmentOrderInfoFieldEnum.payer.getFieldName() , ShipmentOrderInfoFieldEnum.payer.name()) + String.format("%s as %s," , ShipmentOrderInfoFieldEnum.waybillNo.getFieldName() , ShipmentOrderInfoFieldEnum.waybillNo.name()) + - String.format("%s as %s" , ShipmentOrderInfoFieldEnum.expressType.getFieldName() , ShipmentOrderInfoFieldEnum.expressType.name()) + + String.format("%s as %s," , ShipmentOrderInfoFieldEnum.expressType.getFieldName() , ShipmentOrderInfoFieldEnum.expressType.name()) + + String.format("%s as %s" , ShipmentOrderInfoFieldEnum.createdOn.getFieldName() , ShipmentOrderInfoFieldEnum.createdOn.name()) + + " FROM SM_Ship WITH(NOLOCK) WHERE DocNo = ?"; try { @@ -75,7 +77,8 @@ public class ErpServiceImpl implements ErpService { String.format(" a.%s as %s , " , SampleOrderInfoFieldEnum.waybillNo.getFieldName() , SampleOrderInfoFieldEnum.waybillNo.name()) + String.format(" a.%s as %s , " , SampleOrderInfoFieldEnum.expressType.getFieldName() , SampleOrderInfoFieldEnum.expressType.name()) + String.format(" a.%s as %s , " , SampleOrderInfoFieldEnum.fee.getFieldName() , SampleOrderInfoFieldEnum.fee.name()) + - String.format(" a.%s as %s " , SampleOrderInfoFieldEnum.quantity.getFieldName() , SampleOrderInfoFieldEnum.quantity.name()) + + String.format(" a.%s as %s , " , SampleOrderInfoFieldEnum.quantity.getFieldName() , SampleOrderInfoFieldEnum.quantity.name()) + + String.format(" a.%s as %s " , SampleOrderInfoFieldEnum.createdOn.getFieldName() , SampleOrderInfoFieldEnum.createdOn.name()) + "FROM InvDoc_LendTrans a WITH(NOLOCK) " + "WHERE a.Org = '1002011064787026' " + // 指定组织 ID " AND a.Status = 0 " + // 状态为开立/待处理 @@ -129,19 +132,22 @@ public class ErpServiceImpl implements ErpService { // --- 情况 A: 常规出货单 (使用 SM_Ship) --- log.info(">>> [U9回写费用] 正在更新【常规出货单】: {}, 金额: {}", orderNo, formattedAmount); - sql = String.format("UPDATE SM_Ship SET %s = ?, %s = ? WHERE DocNo = ?" , + sql = String.format("UPDATE SM_Ship SET %s = ?, %s = ?,%s = ? WHERE DocNo = ?" , ShipmentOrderInfoFieldEnum.quantity.getFieldName() , - ShipmentOrderInfoFieldEnum.fee.getFieldName()); - rows = u9JdbcTemplate.update(sql, qty, formattedAmount, orderNo); + ShipmentOrderInfoFieldEnum.fee.getFieldName() , + ShipmentOrderInfoFieldEnum.freightPhone.getFieldName()); + rows = u9JdbcTemplate.update(sql, qty, formattedAmount , "95338", orderNo); } else { // --- 情况 B: 样品单/借出单 (使用 InvDoc_LendTrans) --- log.info(">>> [U9回写费用] 正在更新【样品单】: {}, 金额: {}", orderNo, formattedAmount); - sql = String.format("UPDATE InvDoc_LendTrans SET %s = ?, %s = ? WHERE DocNo = ?" , + sql = String.format("UPDATE InvDoc_LendTrans SET %s = ?, %s = ? , %s = ? WHERE DocNo = ?" , SampleOrderInfoFieldEnum.quantity.getFieldName() , - SampleOrderInfoFieldEnum.fee.getFieldName()); - rows = u9JdbcTemplate.update(sql, qty, formattedAmount, orderNo); + SampleOrderInfoFieldEnum.fee.getFieldName() , + SampleOrderInfoFieldEnum.freightPhone.getFieldName() + ); + rows = u9JdbcTemplate.update(sql, qty, formattedAmount, "95338" , orderNo); } if (rows == 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 415e0c6..8993f54 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 @@ -118,6 +118,8 @@ public class SfCreateOrderHandler implements ApiTaskHandler { cargoDetails.add(item); msgData.put("cargoDetails", cargoDetails); + msgData.put("remark" , u9Data.getString(ShipmentOrderInfoFieldEnum.createdOn.name())); + // 2.1 构造收寄双方信息 (根据你之前 SELECT * 看到的 U9 字段名来取值) JSONArray contactInfoList = new JSONArray(); diff --git a/src/main/java/com/project/receive/controller/FaviconController.java b/src/main/java/com/project/receive/controller/FaviconController.java new file mode 100644 index 0000000..446f2a4 --- /dev/null +++ b/src/main/java/com/project/receive/controller/FaviconController.java @@ -0,0 +1,12 @@ +package com.project.receive.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class FaviconController { + @GetMapping("favicon.ico") + void returnNoFavicon() { + // 映射此路径但不做任何操作,默认返回 200 或 204 + } +} \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..ee21c5f --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,113 @@ +server: + port: 9088 +spring: + main: + # 允许 Bean 覆盖,解决 dynamic-datasource 与 JPA 的初始化冲突 + allow-bean-definition-overriding: true + datasource: + url: jdbc:mysql://192.168.5.31:3306/auto_logistics?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false + username: root + password: Itc@123456 + driver-class-name: com.mysql.cj.jdbc.Driver + + # dynamic: + # primary: master + # datasource: + # master: + # driverClassName: com.mysql.cj.jdbc.Driver + # password: Itc@123456 + # url: jdbc:mysql://8.129.84.155:3306/auto_logistics?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false + # username: logistics_admin + + data: + redis: + host: 192.168.5.31 + port: 6379 + password: Itc@123456 + database: 5 + timeout: 5000ms + lettuce: + pool: + max-active: 8 + max-idle: 30 + max-wait: 10000 + min-idle: 10 + jpa: + hibernate: + # 确保是 update + ddl-auto: update + # 显式指定数据库平台 + database-platform: org.hibernate.dialect.MySQL8Dialect + show-sql: true + # 关键:告诉 Hibernate 自动扫描实体类 + open-in-view: true + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + # 显式指定命名策略,防止大小写或下划线解析错误 + physical_strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl +sf: + api: + partnerId: Y847O1KA + secret: DgNS7ANgynXYcnhvu4moAe4rCAHkWvk9 + tokenUrl: https://bspgw.sf-express.com/oauth2/accessToken + baseUrl: https://bspgw.sf-express.com/std/service + channelCode: INC-VMOS-CORE + podSecret: b4fb275c23204056 +mybatis-plus: + configuration: + # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境下打印SQL + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + map-underscore-to-camel-case: true # 开启驼峰命名 + global-config: + db-config: + id-type: assign_id # 使用雪花算法生成本地订单主键ID +webdav: + url: "http://192.168.5.31:8081" + username: "admin" + password: "123456" +logistics: + scanner: + enabled: true + # 允许在 10点、15-20点的 第 8 分和第 28 分唤醒 + # 唤醒时间点:10:08, 10:28, 15:08, 15:28, 16:08, 16:28 ... 20:28 + cron: "0 2,32 10-19 * * ?" + windows: + - name: "日间半小时波次" + # 10:00 开始,确保 10:02 第一波顺利执行 + startTime: "10:00" + # 【关键】设置为 19:15,这样 19:02 会执行,但 19:32 因为超过了 19:15 会被拦截 + endTime: "19:15" + # 设置为 25,确保 02 分和 32 分(30分钟间隔)能通过频率检查 + intervalMinutes: 25 + sample-scanner: + enabled: true + cron: "0 2,32 10-19 * * ?" + windows: + - name: "日间半小时波次" + # 10:00 开始,确保 10:02 第一波顺利执行 + startTime: "10:00" + # 【关键】设置为 19:15,这样 19:02 会执行,但 19:32 因为超过了 19:15 会被拦截 + endTime: "19:15" + # 设置为 25,确保 02 分和 32 分(30分钟间隔)能通过频率检查 + intervalMinutes: 25 +logging: + level: + # 强制打印 Hibernate 初始化过程 + org.hibernate.SQL: debug + org.hibernate.orm.deprecation: error + org.hibernate.tool.schema: debug + # 看看 Spring 到底有没有加载 JPA + org.springframework.orm.jpa: debug +u9-source: + url: jdbc:sqlserver://192.168.4.206;databaseName=DataCenter;encrypt=false;trustServerCertificate=true + username: sa + password: 'Hello2018.world' + driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver +fixed-rule: + senderPayContact: '王盛荣' + senderMobile: '18998809792' + senderAddress: '广东省广州市番禺区石碁镇南荔东路56号' + monthlyCard: '0200467987' +scheduled-task: + owner: prod \ No newline at end of file