|
|
@@ -8,10 +8,9 @@ import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
+
|
|
|
import java.time.Instant;
|
|
|
-import java.util.Arrays;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.List;
|
|
|
+import java.util.*;
|
|
|
|
|
|
/**
|
|
|
* 聚水潭[兔灵]订单同步接口
|
|
|
@@ -21,24 +20,27 @@ import java.util.List;
|
|
|
@RequestMapping("/sync/order/jst")
|
|
|
public class JstOrderSyncController {
|
|
|
|
|
|
-
|
|
|
@Autowired
|
|
|
private TlErpOrderService jstErpHttpService;
|
|
|
|
|
|
@Autowired
|
|
|
private ObjectMapper objectMapper;
|
|
|
|
|
|
+ //聚水潭官方文档说对于回调请求 partnerKey 和 partnerid 的值相同
|
|
|
+ private static final String PARTNER_ID = "erp";
|
|
|
+ private static final String PARTNER_KEY = "erp";
|
|
|
|
|
|
- private static final String partnerKey="erp";
|
|
|
+ private static final Set<String> SUPPORTED_METHODS;
|
|
|
|
|
|
+ static {
|
|
|
+ Set<String> supported = new HashSet<>();
|
|
|
+ supported.add("logistics.upload");
|
|
|
+ supported.add("cancel.order");
|
|
|
+ supported.add("inventory.upload");
|
|
|
+ supported.add("refund.goods");
|
|
|
+ SUPPORTED_METHODS = Collections.unmodifiableSet(supported);
|
|
|
+ }
|
|
|
|
|
|
- private static final List<String> SUPPORTED_METHODS =
|
|
|
- Collections.unmodifiableList(Arrays.asList(
|
|
|
- "logistics.upload",
|
|
|
- "cancel.order",
|
|
|
- "inventory.upload",
|
|
|
- "refund.goods"
|
|
|
- ));
|
|
|
/**
|
|
|
* 聚水潭erp物流回调接口
|
|
|
* URL: POST /open/callback?partnerid=erp&method=logistics.upload&ts=xxx&sign=xxx
|
|
|
@@ -50,55 +52,38 @@ public class JstOrderSyncController {
|
|
|
@RequestParam String method,
|
|
|
@RequestParam Long ts,
|
|
|
@RequestParam String sign,
|
|
|
- // 注意:订单取消接口还有 token,但其他没有。聚水潭文档说 cancel.order 有 token
|
|
|
- @RequestParam(required = false) String token,
|
|
|
+ @RequestParam(required = false) String token, // token 未在当前逻辑中使用
|
|
|
@RequestBody String rawBody) {
|
|
|
|
|
|
- // 1. 校验 partnerid
|
|
|
- if (!"erp".equals(partnerid)) {
|
|
|
- return ResponseEntity.ok(new PushResponse("1", "invalid partnerid"));
|
|
|
- }
|
|
|
+ log.info("收到聚水潭[兔灵]回调, method={}, ts={}, rawBody={}", method, ts, rawBody);
|
|
|
|
|
|
- // 2. 校验 method
|
|
|
- if (!SUPPORTED_METHODS.contains(method)) {
|
|
|
- log.warn("不支持的 method: {}", method);
|
|
|
- return ResponseEntity.ok(new PushResponse("1", "unsupported method"));
|
|
|
+ // 1. 调用封装的校验方法
|
|
|
+ ResponseEntity<PushResponse> validationResponse = validateRequest(partnerid, method, ts, sign);
|
|
|
+ if (validationResponse != null) {
|
|
|
+ // 如果校验失败,直接返回错误响应
|
|
|
+ return validationResponse;
|
|
|
}
|
|
|
|
|
|
- // 3. 校验时间戳
|
|
|
- long now = Instant.now().getEpochSecond();
|
|
|
- if (Math.abs(now - ts) > 600) {
|
|
|
- return ResponseEntity.ok(new PushResponse("1", "ts expired"));
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 验签(注意:sign 拼接规则不含 token!)
|
|
|
- String signSource = method + partnerid + "ts" + ts + partnerKey;
|
|
|
- String expectedSign = DigestUtils.md5Hex(signSource).toLowerCase();
|
|
|
- if (!expectedSign.equals(sign)) {
|
|
|
- log.warn("签名校验失败: method={}, ts={}", method, ts);
|
|
|
- return ResponseEntity.ok(new PushResponse("1", "invalid sign"));
|
|
|
- }
|
|
|
-
|
|
|
- // 5. 根据 method 反序列化并处理
|
|
|
+ // 2. 根据 method 反序列化并处理
|
|
|
try {
|
|
|
if ("inventory.upload".equals(method)) {
|
|
|
- log.info("处理聚水潭库存同步回调");
|
|
|
+ //处理聚水潭库存同步回调
|
|
|
JstInventoryPushDTO req = objectMapper.readValue(rawBody, JstInventoryPushDTO.class);
|
|
|
jstErpHttpService.jSTanErpInventoryCallback(req);
|
|
|
} else if ("cancel.order".equals(method)) {
|
|
|
- log.info("处理聚水潭取消订单回调");
|
|
|
+ //处理聚水潭取消订单回调
|
|
|
JstCancelOrderDTO req = objectMapper.readValue(rawBody, JstCancelOrderDTO.class);
|
|
|
jstErpHttpService.jSTanErpCancelOrderCallback(req);
|
|
|
- }
|
|
|
- else if ("logistics.upload".equals(method)) {
|
|
|
- log.info("处理聚水潭物流同步回调");
|
|
|
+ } else if ("logistics.upload".equals(method)) {
|
|
|
+ //处理聚水潭物流同步回调
|
|
|
JstLogisticsPushDTO req = objectMapper.readValue(rawBody, JstLogisticsPushDTO.class);
|
|
|
jstErpHttpService.jSTanErpLogisticsCallback(req);
|
|
|
- }else if ("refund.goods".equals(method)) {
|
|
|
- log.info("处理聚水潭售后收货回调");
|
|
|
+ } else if ("refund.goods".equals(method)) {
|
|
|
+ //处理聚水潭售后收货回调
|
|
|
JstRefundGoodsDTO req = objectMapper.readValue(rawBody, JstRefundGoodsDTO.class);
|
|
|
jstErpHttpService.jSTanErpRefundGoodsCallback(req);
|
|
|
} else {
|
|
|
+ // 理论上这步不会走到,因为 validateRequest 已经检查过了
|
|
|
return ResponseEntity.ok(new PushResponse("1", "unknown method"));
|
|
|
}
|
|
|
return ResponseEntity.ok(new PushResponse("0", "执行成功"));
|
|
|
@@ -107,4 +92,45 @@ public class JstOrderSyncController {
|
|
|
return ResponseEntity.ok(new PushResponse("1", "system error"));
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 封装的请求校验方法
|
|
|
+ *
|
|
|
+ * @param partnerid 请求中的 partnerid
|
|
|
+ * @param method 请求中的 method
|
|
|
+ * @param ts 请求中的时间戳
|
|
|
+ * @param sign 请求中的签名
|
|
|
+ * @return 如果校验失败,返回包含错误信息的 ResponseEntity;如果校验成功,返回 null。
|
|
|
+ */
|
|
|
+ private ResponseEntity<PushResponse> validateRequest(String partnerid, String method, Long ts, String sign) {
|
|
|
+ // 1. 校验 partnerid
|
|
|
+ if (!PARTNER_ID.equals(partnerid)) {
|
|
|
+ log.warn("校验失败: invalid partnerid={}", partnerid);
|
|
|
+ return ResponseEntity.ok(new PushResponse("1", "invalid partnerid"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 校验 method
|
|
|
+ if (!SUPPORTED_METHODS.contains(method)) {
|
|
|
+ log.warn("校验失败: 不支持的 method={}", method);
|
|
|
+ return ResponseEntity.ok(new PushResponse("1", "unsupported method"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 校验时间戳
|
|
|
+ long now = Instant.now().getEpochSecond();
|
|
|
+ if (Math.abs(now - ts) > 600) { // 允许 10 分钟的时间差
|
|
|
+ log.warn("校验失败: ts expired, now={}, ts={}", now, ts);
|
|
|
+ return ResponseEntity.ok(new PushResponse("1", "ts expired"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 验签(注意:sign 拼接规则不含 token!)
|
|
|
+ String signSource = method + partnerid + "ts" + ts + PARTNER_KEY;
|
|
|
+ String expectedSign = DigestUtils.md5Hex(signSource).toLowerCase();
|
|
|
+ if (!expectedSign.equals(sign)) {
|
|
|
+ log.warn("校验失败: 签名校验失败, method={}, ts={}, expectedSign={}, receivedSign={}", method, ts, expectedSign, sign);
|
|
|
+ return ResponseEntity.ok(new PushResponse("1", "invalid sign"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 所有校验通过
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|