1
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.ruoyi.web.controller.common;
|
package com.ruoyi.web.controller.common;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.ruoyi.common.annotation.Anonymous;
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
@@ -15,7 +16,8 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 闲管家开放平台推送回调(请在开放平台填写真实 URL)
|
* 闲管家开放平台推送回调(请在开放平台填写真实 URL)
|
||||||
* 订单:建议 POST .../open/callback/order/receive
|
* 订单:POST .../open/callback/order/receive?appid=×tamp=&sign=
|
||||||
|
* 响应须与平台「强校验」一致:一般 {@code {"code":0,"msg":"OK","data":{}}} ,勿使用 {@code result} 等非标准字段。
|
||||||
*/
|
*/
|
||||||
@Anonymous
|
@Anonymous
|
||||||
@RestController
|
@RestController
|
||||||
@@ -32,61 +34,95 @@ public class OpenCallbackController {
|
|||||||
public JSONObject receiveProductCallback(
|
public JSONObject receiveProductCallback(
|
||||||
@RequestParam("appid") String appid,
|
@RequestParam("appid") String appid,
|
||||||
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
||||||
|
@RequestParam(value = "seller_id", required = false) Long sellerId,
|
||||||
@RequestParam("sign") String sign,
|
@RequestParam("sign") String sign,
|
||||||
@RequestBody JSONObject body
|
@RequestBody(required = false) String rawBody
|
||||||
) {
|
) {
|
||||||
|
String normalizedBody = normalizeJsonBody(rawBody);
|
||||||
IERPAccount account = erpAccountResolver.resolveStrict(appid);
|
IERPAccount account = erpAccountResolver.resolveStrict(appid);
|
||||||
if (!verifySign(account, timestamp, sign, body)) {
|
if (!verifyGoofishSign(account, timestamp, sellerId, sign, normalizedBody)) {
|
||||||
JSONObject fail = new JSONObject();
|
return failCallback(1, "签名失败");
|
||||||
fail.put("result", "fail");
|
|
||||||
fail.put("msg", "签名失败");
|
|
||||||
return fail;
|
|
||||||
}
|
}
|
||||||
JSONObject ok = new JSONObject();
|
return successCallback();
|
||||||
ok.put("result", "success");
|
|
||||||
ok.put("msg", "接收成功");
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/order/receive")
|
@PostMapping("/order/receive")
|
||||||
public JSONObject receiveOrderCallback(
|
public JSONObject receiveOrderCallback(
|
||||||
@RequestParam("appid") String appid,
|
@RequestParam("appid") String appid,
|
||||||
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
@RequestParam(value = "timestamp", required = false) Long timestamp,
|
||||||
|
@RequestParam(value = "seller_id", required = false) Long sellerId,
|
||||||
@RequestParam("sign") String sign,
|
@RequestParam("sign") String sign,
|
||||||
@RequestBody JSONObject body
|
@RequestBody(required = false) String rawBody
|
||||||
) {
|
) {
|
||||||
|
String normalizedBody = normalizeJsonBody(rawBody);
|
||||||
IERPAccount account = erpAccountResolver.resolveStrict(appid);
|
IERPAccount account = erpAccountResolver.resolveStrict(appid);
|
||||||
if (!verifySign(account, timestamp, sign, body)) {
|
if (account == null) {
|
||||||
JSONObject fail = new JSONObject();
|
return failCallback(1, "未找到启用的 AppKey 配置");
|
||||||
fail.put("result", "fail");
|
}
|
||||||
fail.put("msg", "签名失败");
|
if (!verifyGoofishSign(account, timestamp, sellerId, sign, normalizedBody)) {
|
||||||
return fail;
|
return failCallback(1, "签名失败");
|
||||||
|
}
|
||||||
|
JSONObject body;
|
||||||
|
try {
|
||||||
|
body = "{}".equals(normalizedBody) ? new JSONObject() : JSON.parseObject(normalizedBody);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return failCallback(2, "请求体不是合法JSON");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
erpGoofishOrderService.publishOrProcessNotify(appid, timestamp, body);
|
erpGoofishOrderService.publishOrProcessNotify(appid, timestamp, body);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JSONObject fail = new JSONObject();
|
return failCallback(3, "入队异常");
|
||||||
fail.put("result", "fail");
|
|
||||||
fail.put("msg", "入队异常");
|
|
||||||
return fail;
|
|
||||||
}
|
}
|
||||||
JSONObject ok = new JSONObject();
|
return successCallback();
|
||||||
ok.put("result", "success");
|
|
||||||
ok.put("msg", "接收成功");
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean verifySign(IERPAccount account, Long timestamp, String sign, JSONObject body) {
|
/**
|
||||||
|
* 与开放平台示例一致:签名字符串 = md5(appKey + "," + md5(body原文) + "," + timestamp + [,sellerId] + "," + appSecret)
|
||||||
|
* body 须与推送原文完全一致后做 MD5,不能用解析后再 toJSONString(字段顺序变化会导致验签失败)。
|
||||||
|
*/
|
||||||
|
private boolean verifyGoofishSign(IERPAccount account, Long timestamp, Long sellerId, String sign, String bodyExactForMd5) {
|
||||||
if (account == null || StringUtils.isEmpty(sign)) {
|
if (account == null || StringUtils.isEmpty(sign)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String json = body == null ? "{}" : body.toJSONString();
|
String jsonForMd5 = bodyExactForMd5 == null || bodyExactForMd5.isEmpty() ? "{}" : bodyExactForMd5;
|
||||||
String data = account.getApiKey() + "," + md5(json) + "," + (timestamp == null ? 0 : timestamp) + "," + account.getApiKeySecret();
|
String bodyMd5 = md5Hex(jsonForMd5);
|
||||||
String local = md5(data);
|
long ts = timestamp == null ? 0L : timestamp;
|
||||||
return StringUtils.equalsIgnoreCase(local, sign);
|
String data;
|
||||||
|
if (sellerId != null) {
|
||||||
|
data = account.getApiKey() + "," + bodyMd5 + "," + ts + "," + sellerId + "," + account.getApiKeySecret();
|
||||||
|
} else {
|
||||||
|
data = account.getApiKey() + "," + bodyMd5 + "," + ts + "," + account.getApiKeySecret();
|
||||||
|
}
|
||||||
|
String local = md5Hex(data);
|
||||||
|
return StringUtils.equalsIgnoreCase(local, sign.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String md5(String str) {
|
private static String normalizeJsonBody(String rawBody) {
|
||||||
|
if (rawBody == null) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
String t = rawBody.trim();
|
||||||
|
return t.isEmpty() ? "{}" : t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 与平台开放接口成功响应字段类型对齐:code 为数值、msg 为字符串、data 为对象 */
|
||||||
|
private static JSONObject successCallback() {
|
||||||
|
JSONObject ok = new JSONObject();
|
||||||
|
ok.put("code", 0);
|
||||||
|
ok.put("msg", "OK");
|
||||||
|
ok.put("data", new JSONObject());
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONObject failCallback(int code, String msg) {
|
||||||
|
JSONObject j = new JSONObject();
|
||||||
|
j.put("code", code);
|
||||||
|
j.put("msg", msg == null ? "fail" : msg);
|
||||||
|
j.put("data", new JSONObject());
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String md5Hex(String str) {
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
|
byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|||||||
@@ -52,4 +52,17 @@ public class JarvisGoofishProperties {
|
|||||||
private int pullFullHistoryDays = 1095;
|
private int pullFullHistoryDays = 1095;
|
||||||
|
|
||||||
private int autoShipBatchSize = 20;
|
private int autoShipBatchSize = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许触发闲鱼开放平台「发货」的本地 order_status(逗号分隔,与推送/列表一致)。默认 12 通常表示待发货。
|
||||||
|
*/
|
||||||
|
private String autoShipOrderStatuses = "12";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未在 erp_open_config 配置 express_code 时,自动发货使用的默认快递公司编码(官方列表中日日顺多为 rrs)。
|
||||||
|
*/
|
||||||
|
private String defaultShipExpressCode = "rrs";
|
||||||
|
|
||||||
|
/** 与 defaultShipExpressCode 配套的展示名称 */
|
||||||
|
private String defaultShipExpressName = "日日顺";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public interface ErpGoofishOrderMapper {
|
|||||||
|
|
||||||
int update(ErpGoofishOrder row);
|
int update(ErpGoofishOrder row);
|
||||||
|
|
||||||
List<ErpGoofishOrder> selectPendingShip(@Param("limit") int limit);
|
List<ErpGoofishOrder> selectPendingShip(@Param("statuses") List<Integer> statuses, @Param("limit") int limit);
|
||||||
|
|
||||||
int resetShipForRetry(@Param("id") Long id);
|
int resetShipForRetry(@Param("id") Long id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,4 +34,9 @@ public interface IErpGoofishOrderService {
|
|||||||
int syncWaybillAndTryShipBatch(int limit);
|
int syncWaybillAndTryShipBatch(int limit);
|
||||||
|
|
||||||
void applyListOrNotifyItem(String appKey, JSONObject item, String lastNotifyJson);
|
void applyListOrNotifyItem(String appKey, JSONObject item, String lastNotifyJson);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 京东单物流扫描已得到运单号并写入 Redis 后调用:同步到闲鱼单并尝试开放平台发货。
|
||||||
|
*/
|
||||||
|
void notifyJdWaybillReady(Long jdOrderId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ public class GoofishOrderPipeline {
|
|||||||
|
|
||||||
public void runFullPipeline(String appid, JSONObject notifyBody) {
|
public void runFullPipeline(String appid, JSONObject notifyBody) {
|
||||||
try {
|
try {
|
||||||
|
JSONObject shape = sourceForNotifyUpsert(notifyBody);
|
||||||
ErpGoofishOrder row = upsertFromNotify(appid, notifyBody, notifyBody.toJSONString());
|
ErpGoofishOrder row = upsertFromNotify(appid, notifyBody, notifyBody.toJSONString());
|
||||||
tryLinkJdOrder(row);
|
tryLinkJdOrder(row);
|
||||||
mergeSummaryFromOrderDetailShape(row, notifyBody);
|
mergeSummaryFromOrderDetailShape(row, shape);
|
||||||
refreshDetail(row);
|
refreshDetail(row);
|
||||||
syncWaybillFromRedis(row);
|
syncWaybillFromRedis(row);
|
||||||
tryAutoShip(row);
|
tryAutoShip(row);
|
||||||
@@ -74,24 +75,29 @@ public class GoofishOrderPipeline {
|
|||||||
tryAutoShip(row);
|
tryAutoShip(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErpGoofishOrder upsertFromNotify(String appKey, JSONObject body, String lastNotifyJson) {
|
public ErpGoofishOrder upsertFromNotify(String appKey, JSONObject rawBody, String lastNotifyJson) {
|
||||||
|
JSONObject body = sourceForNotifyUpsert(rawBody);
|
||||||
Date now = DateUtils.getNowDate();
|
Date now = DateUtils.getNowDate();
|
||||||
String orderNo = body.getString("order_no");
|
String orderNo = notifyFirstString(body, "order_no", "orderNo");
|
||||||
if (StringUtils.isEmpty(orderNo)) {
|
if (StringUtils.isEmpty(orderNo)) {
|
||||||
throw new IllegalArgumentException("缺少 order_no");
|
throw new IllegalArgumentException("缺少 order_no");
|
||||||
}
|
}
|
||||||
ErpGoofishOrder existing = erpGoofishOrderMapper.selectByAppKeyAndOrderNo(appKey, orderNo);
|
ErpGoofishOrder existing = erpGoofishOrderMapper.selectByAppKeyAndOrderNo(appKey, orderNo);
|
||||||
ErpGoofishOrder e = new ErpGoofishOrder();
|
ErpGoofishOrder e = new ErpGoofishOrder();
|
||||||
e.setAppKey(appKey);
|
e.setAppKey(appKey);
|
||||||
e.setSellerId(body.getLong("seller_id"));
|
Long sid = notifyFirstLong(body, "seller_id", "sellerId");
|
||||||
e.setUserName(body.getString("user_name"));
|
e.setSellerId(sid);
|
||||||
|
e.setUserName(notifyFirstString(body, "user_name", "userName", "seller_name"));
|
||||||
e.setOrderNo(orderNo);
|
e.setOrderNo(orderNo);
|
||||||
e.setOrderType(body.getInteger("order_type"));
|
e.setOrderType(notifyFirstInteger(body, "order_type", "orderType"));
|
||||||
e.setOrderStatus(body.getInteger("order_status"));
|
e.setOrderStatus(notifyFirstInteger(body, "order_status", "orderStatus"));
|
||||||
e.setRefundStatus(body.getInteger("refund_status"));
|
e.setRefundStatus(notifyFirstInteger(body, "refund_status", "refundStatus"));
|
||||||
e.setModifyTime(body.getLong("modify_time"));
|
Long mt = notifyFirstLong(body, "modify_time", "order_modify_time", "update_time", "modifyTime");
|
||||||
e.setProductId(body.getLong("product_id"));
|
e.setModifyTime(mt);
|
||||||
e.setItemId(body.getLong("item_id"));
|
Long pid = notifyFirstLong(body, "product_id", "productId");
|
||||||
|
e.setProductId(pid);
|
||||||
|
Long iid = notifyFirstLong(body, "item_id", "itemId");
|
||||||
|
e.setItemId(iid);
|
||||||
e.setLastNotifyJson(lastNotifyJson);
|
e.setLastNotifyJson(lastNotifyJson);
|
||||||
e.setUpdateTime(now);
|
e.setUpdateTime(now);
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
@@ -254,11 +260,11 @@ public class GoofishOrderPipeline {
|
|||||||
patch.setReceiverAddress(recv.getString("address"));
|
patch.setReceiverAddress(recv.getString("address"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Integer os = data.getInteger("order_status");
|
Integer os = firstInt(data, "order_status", "orderStatus");
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
patch.setOrderStatus(os);
|
patch.setOrderStatus(os);
|
||||||
}
|
}
|
||||||
Integer rs = data.getInteger("refund_status");
|
Integer rs = firstInt(data, "refund_status", "refundStatus");
|
||||||
if (rs != null) {
|
if (rs != null) {
|
||||||
patch.setRefundStatus(rs);
|
patch.setRefundStatus(rs);
|
||||||
}
|
}
|
||||||
@@ -394,7 +400,8 @@ public class GoofishOrderPipeline {
|
|||||||
if (row == null || row.getId() == null) {
|
if (row == null || row.getId() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (row.getOrderStatus() == null || row.getOrderStatus() != 12) {
|
List<Integer> awaiting = resolveAutoShipOrderStatuses();
|
||||||
|
if (row.getOrderStatus() == null || !awaiting.contains(row.getOrderStatus())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (row.getRefundStatus() != null && row.getRefundStatus() != 0) {
|
if (row.getRefundStatus() != null && row.getRefundStatus() != 0) {
|
||||||
@@ -411,10 +418,18 @@ public class GoofishOrderPipeline {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ErpOpenConfig cfg = erpOpenConfigService.selectByAppKey(row.getAppKey());
|
ErpOpenConfig cfg = erpOpenConfigService.selectByAppKey(row.getAppKey());
|
||||||
String expressCode = cfg != null ? cfg.getExpressCode() : null;
|
String expressCode = cfg != null && StringUtils.isNotEmpty(cfg.getExpressCode()) ? cfg.getExpressCode() : null;
|
||||||
String expressName = cfg != null && StringUtils.isNotEmpty(cfg.getExpressName()) ? cfg.getExpressName() : "日日顺";
|
|
||||||
if (StringUtils.isEmpty(expressCode)) {
|
if (StringUtils.isEmpty(expressCode)) {
|
||||||
log.info("闲管家自动发货跳过:未配置 express_code appKey={} orderNo={}", row.getAppKey(), row.getOrderNo());
|
expressCode = goofishProperties.getDefaultShipExpressCode();
|
||||||
|
}
|
||||||
|
// 业务约定:对闲鱼回传物流名称固定为「日日顺」(编码以配置 / 默认 rrs 为准)
|
||||||
|
String expressName = goofishProperties.getDefaultShipExpressName();
|
||||||
|
if (StringUtils.isEmpty(expressName)) {
|
||||||
|
expressName = "日日顺";
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(expressCode)) {
|
||||||
|
log.info("闲管家自动发货跳过:无快递公司编码(请配置 erp_open_config.express_code 或 jarvis.goofish-order.default-ship-express-code) appKey={} orderNo={}",
|
||||||
|
row.getAppKey(), row.getOrderNo());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ShipAddressParts addr = parseShipAddress(row.getDetailJson());
|
ShipAddressParts addr = parseShipAddress(row.getDetailJson());
|
||||||
@@ -656,6 +671,91 @@ public class GoofishOrderPipeline {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Integer> resolveAutoShipOrderStatuses() {
|
||||||
|
String raw = goofishProperties.getAutoShipOrderStatuses();
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
if (StringUtils.isNotEmpty(raw)) {
|
||||||
|
for (String part : raw.split(",")) {
|
||||||
|
String t = part.trim();
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
list.add(Integer.parseInt(t));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// ignore invalid token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
list.add(12);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送回调体可能是扁平订单字段,也可能包在 data / data.list[0] / order 中(与列表项 schema 对齐的优先解包)。
|
||||||
|
*/
|
||||||
|
private static JSONObject sourceForNotifyUpsert(JSONObject rawBody) {
|
||||||
|
if (rawBody == null) {
|
||||||
|
return new JSONObject();
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(rawBody.getString("order_no")) || StringUtils.isNotEmpty(rawBody.getString("orderNo"))) {
|
||||||
|
return rawBody;
|
||||||
|
}
|
||||||
|
JSONObject data = rawBody.getJSONObject("data");
|
||||||
|
if (data != null) {
|
||||||
|
if (StringUtils.isNotEmpty(data.getString("order_no")) || StringUtils.isNotEmpty(data.getString("orderNo"))) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
JSONObject order = data.getJSONObject("order");
|
||||||
|
if (order != null && (StringUtils.isNotEmpty(order.getString("order_no"))
|
||||||
|
|| StringUtils.isNotEmpty(order.getString("orderNo")))) {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
JSONArray list = data.getJSONArray("list");
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
Object first = list.get(0);
|
||||||
|
if (first instanceof JSONObject) {
|
||||||
|
JSONObject row = (JSONObject) first;
|
||||||
|
if (StringUtils.isNotEmpty(row.getString("order_no")) || StringUtils.isNotEmpty(row.getString("orderNo"))) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSONObject order = rawBody.getJSONObject("order");
|
||||||
|
if (order != null && (StringUtils.isNotEmpty(order.getString("order_no"))
|
||||||
|
|| StringUtils.isNotEmpty(order.getString("orderNo")))) {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
return rawBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String notifyFirstString(JSONObject o, String... keys) {
|
||||||
|
return firstNonEmpty(o, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer notifyFirstInteger(JSONObject o, String... keys) {
|
||||||
|
return firstInt(o, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Long notifyFirstLong(JSONObject o, String... keys) {
|
||||||
|
if (o == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String k : keys) {
|
||||||
|
if (!o.containsKey(k)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long v = o.getLong(k);
|
||||||
|
if (v != null) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增量拉单:按 update_time ∈ [now−lookbackHours, now]
|
* 增量拉单:按 update_time ∈ [now−lookbackHours, now]
|
||||||
*/
|
*/
|
||||||
@@ -837,7 +937,8 @@ public class GoofishOrderPipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int syncWaybillAndTryShipBatch(int limit) {
|
public int syncWaybillAndTryShipBatch(int limit) {
|
||||||
List<ErpGoofishOrder> rows = erpGoofishOrderMapper.selectPendingShip(limit);
|
List<Integer> statuses = resolveAutoShipOrderStatuses();
|
||||||
|
List<ErpGoofishOrder> rows = erpGoofishOrderMapper.selectPendingShip(statuses, limit);
|
||||||
if (rows == null) {
|
if (rows == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,4 +138,25 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService {
|
|||||||
public void applyListOrNotifyItem(String appKey, JSONObject item, String lastNotifyJson) {
|
public void applyListOrNotifyItem(String appKey, JSONObject item, String lastNotifyJson) {
|
||||||
goofishOrderPipeline.applyListOrNotifyItem(appKey, item, lastNotifyJson);
|
goofishOrderPipeline.applyListOrNotifyItem(appKey, item, lastNotifyJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyJdWaybillReady(Long jdOrderId) {
|
||||||
|
if (jdOrderId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ErpGoofishOrder query = new ErpGoofishOrder();
|
||||||
|
query.setJdOrderId(jdOrderId);
|
||||||
|
List<ErpGoofishOrder> list = erpGoofishOrderMapper.selectList(query);
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ErpGoofishOrder ref : list) {
|
||||||
|
ErpGoofishOrder full = erpGoofishOrderMapper.selectById(ref.getId());
|
||||||
|
if (full == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
goofishOrderPipeline.syncWaybillFromRedis(full);
|
||||||
|
goofishOrderPipeline.tryAutoShip(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.ruoyi.common.utils.http.HttpUtils;
|
|||||||
import com.ruoyi.jarvis.domain.JDOrder;
|
import com.ruoyi.jarvis.domain.JDOrder;
|
||||||
import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
|
import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
|
||||||
import com.ruoyi.jarvis.mapper.WeComShareLinkLogisticsJobMapper;
|
import com.ruoyi.jarvis.mapper.WeComShareLinkLogisticsJobMapper;
|
||||||
|
import com.ruoyi.jarvis.service.IErpGoofishOrderService;
|
||||||
import com.ruoyi.jarvis.service.ILogisticsService;
|
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||||
import com.ruoyi.system.service.ISysConfigService;
|
import com.ruoyi.system.service.ISysConfigService;
|
||||||
@@ -82,6 +83,9 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
@Resource
|
@Resource
|
||||||
private IJDOrderService jdOrderService;
|
private IJDOrderService jdOrderService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IErpGoofishOrderService erpGoofishOrderService;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
externalApiUrlTemplate = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=";
|
externalApiUrlTemplate = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=";
|
||||||
@@ -215,6 +219,15 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 京东单已写入 Redis 运单后,联动闲鱼单同步并发货(失败不影响物流主流程) */
|
||||||
|
private void safeNotifyGoofishShip(Long jdOrderId) {
|
||||||
|
try {
|
||||||
|
erpGoofishOrderService.notifyJdWaybillReady(jdOrderId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("闲鱼发货联动异常 jdOrderId={} err={}", jdOrderId, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean fetchLogisticsAndPush(JDOrder order) {
|
public boolean fetchLogisticsAndPush(JDOrder order) {
|
||||||
if (order == null || order.getId() == null) {
|
if (order == null || order.getId() == null) {
|
||||||
@@ -375,6 +388,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
logger.info("订单运单号已存在且一致,说明之前已推送过,跳过重复推送 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
logger.info("订单运单号已存在且一致,说明之前已推送过,跳过重复推送 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||||
// 更新过期时间,确保记录不会过期
|
// 更新过期时间,确保记录不会过期
|
||||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
|
safeNotifyGoofishShip(orderId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,6 +406,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
logger.info("订单创建时间较早({}),且Redis中无记录但已获取到运单号,视为之前已推送过,直接标记为已处理,跳过推送 - 订单ID: {}, waybill_no: {}",
|
logger.info("订单创建时间较早({}),且Redis中无记录但已获取到运单号,视为之前已推送过,直接标记为已处理,跳过推送 - 订单ID: {}, waybill_no: {}",
|
||||||
order.getCreateTime(), orderId, waybillNo);
|
order.getCreateTime(), orderId, waybillNo);
|
||||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
|
safeNotifyGoofishShip(orderId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,6 +433,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
// 更新过期时间,确保记录不会过期
|
// 更新过期时间,确保记录不会过期
|
||||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||||
}
|
}
|
||||||
|
safeNotifyGoofishShip(orderId);
|
||||||
|
|
||||||
// 记录最终处理结果
|
// 记录最终处理结果
|
||||||
if (logisticsLinkUpdated) {
|
if (logisticsLinkUpdated) {
|
||||||
|
|||||||
@@ -79,7 +79,14 @@
|
|||||||
|
|
||||||
<select id="selectPendingShip" resultMap="ErpGoofishOrderResult">
|
<select id="selectPendingShip" resultMap="ErpGoofishOrderResult">
|
||||||
<include refid="selectJoinVo"/>
|
<include refid="selectJoinVo"/>
|
||||||
where e.order_status = 12 and (e.ship_status is null or e.ship_status != 1) and e.jd_order_id is not null
|
where e.jd_order_id is not null
|
||||||
|
and (e.ship_status is null or e.ship_status != 1)
|
||||||
|
<if test="statuses != null and statuses.size() > 0">
|
||||||
|
and e.order_status in
|
||||||
|
<foreach collection="statuses" item="st" open="(" separator="," close=")">
|
||||||
|
#{st}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
order by e.update_time asc
|
order by e.update_time asc
|
||||||
limit #{limit}
|
limit #{limit}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user