This commit is contained in:
van
2026-04-10 00:09:26 +08:00
parent 31e7e6853b
commit 6f482256c5
8 changed files with 251 additions and 52 deletions

View File

@@ -1,5 +1,6 @@
package com.ruoyi.web.controller.common;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.utils.StringUtils;
@@ -15,7 +16,8 @@ import java.security.NoSuchAlgorithmException;
/**
* 闲管家开放平台推送回调(请在开放平台填写真实 URL
* 订单:建议 POST .../open/callback/order/receive
* 订单POST .../open/callback/order/receive?appid=&timestamp=&sign=
* 响应须与平台「强校验」一致:一般 {@code {"code":0,"msg":"OK","data":{}}} ,勿使用 {@code result} 等非标准字段。
*/
@Anonymous
@RestController
@@ -32,61 +34,95 @@ public class OpenCallbackController {
public JSONObject receiveProductCallback(
@RequestParam("appid") String appid,
@RequestParam(value = "timestamp", required = false) Long timestamp,
@RequestParam(value = "seller_id", required = false) Long sellerId,
@RequestParam("sign") String sign,
@RequestBody JSONObject body
@RequestBody(required = false) String rawBody
) {
String normalizedBody = normalizeJsonBody(rawBody);
IERPAccount account = erpAccountResolver.resolveStrict(appid);
if (!verifySign(account, timestamp, sign, body)) {
JSONObject fail = new JSONObject();
fail.put("result", "fail");
fail.put("msg", "签名失败");
return fail;
if (!verifyGoofishSign(account, timestamp, sellerId, sign, normalizedBody)) {
return failCallback(1, "签名失败");
}
JSONObject ok = new JSONObject();
ok.put("result", "success");
ok.put("msg", "接收成功");
return ok;
return successCallback();
}
@PostMapping("/order/receive")
public JSONObject receiveOrderCallback(
@RequestParam("appid") String appid,
@RequestParam(value = "timestamp", required = false) Long timestamp,
@RequestParam(value = "seller_id", required = false) Long sellerId,
@RequestParam("sign") String sign,
@RequestBody JSONObject body
@RequestBody(required = false) String rawBody
) {
String normalizedBody = normalizeJsonBody(rawBody);
IERPAccount account = erpAccountResolver.resolveStrict(appid);
if (!verifySign(account, timestamp, sign, body)) {
JSONObject fail = new JSONObject();
fail.put("result", "fail");
fail.put("msg", "签名失败");
return fail;
if (account == null) {
return failCallback(1, "未找到启用的 AppKey 配置");
}
if (!verifyGoofishSign(account, timestamp, sellerId, sign, normalizedBody)) {
return failCallback(1, "签名失败");
}
JSONObject body;
try {
body = "{}".equals(normalizedBody) ? new JSONObject() : JSON.parseObject(normalizedBody);
} catch (Exception e) {
return failCallback(2, "请求体不是合法JSON");
}
try {
erpGoofishOrderService.publishOrProcessNotify(appid, timestamp, body);
} catch (Exception e) {
JSONObject fail = new JSONObject();
fail.put("result", "fail");
fail.put("msg", "入队异常");
return fail;
return failCallback(3, "入队异常");
}
JSONObject ok = new JSONObject();
ok.put("result", "success");
ok.put("msg", "接收成功");
return ok;
return successCallback();
}
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)) {
return false;
}
String json = body == null ? "{}" : body.toJSONString();
String data = account.getApiKey() + "," + md5(json) + "," + (timestamp == null ? 0 : timestamp) + "," + account.getApiKeySecret();
String local = md5(data);
return StringUtils.equalsIgnoreCase(local, sign);
String jsonForMd5 = bodyExactForMd5 == null || bodyExactForMd5.isEmpty() ? "{}" : bodyExactForMd5;
String bodyMd5 = md5Hex(jsonForMd5);
long ts = timestamp == null ? 0L : timestamp;
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 {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));