1
This commit is contained in:
@@ -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=×tamp=&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));
|
||||
|
||||
Reference in New Issue
Block a user