|
@@ -37,11 +37,16 @@ import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
import java.math.RoundingMode;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.security.InvalidKeyException;
|
|
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
-import java.util.HashMap;
|
|
|
|
|
-import java.util.List;
|
|
|
|
|
-import java.util.Map;
|
|
|
|
|
|
|
+import java.util.*;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+import javax.crypto.Mac;
|
|
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
|
|
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
@Service
|
|
@Service
|
|
@@ -91,6 +96,12 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public void handleMessage(Map<String, Object> params) {
|
|
public void handleMessage(Map<String, Object> params) {
|
|
|
|
|
+ // 签名验证:确保回调来自哈哈平台
|
|
|
|
|
+ if (!validateSign(params)) {
|
|
|
|
|
+ log.warn("消息回调签名验证失败,忽略该通知");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
String notifyType = extractParam(params, "notify_type");
|
|
String notifyType = extractParam(params, "notify_type");
|
|
|
if (notifyType == null || notifyType.isEmpty()) {
|
|
if (notifyType == null || notifyType.isEmpty()) {
|
|
|
log.warn("消息回调缺少 notify_type 参数");
|
|
log.warn("消息回调缺少 notify_type 参数");
|
|
@@ -390,6 +401,17 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
order.setOrderType(recognizeType);
|
|
order.setOrderType(recognizeType);
|
|
|
orderService.updateById(order);
|
|
orderService.updateById(order);
|
|
|
log.info("订单类型设置成功 - orderId: {}, orderType: {}", order.getId(), recognizeType);
|
|
log.info("订单类型设置成功 - orderId: {}, orderType: {}", order.getId(), recognizeType);
|
|
|
|
|
+
|
|
|
|
|
+ // IN类型(异物放入)触发告警,提醒管理员审核
|
|
|
|
|
+ if ("IN".equals(recognizeType)) {
|
|
|
|
|
+ String alertContent = String.format("AI识别到异物放入(IN类型),订单号:%s", order.getOrderNo());
|
|
|
|
|
+ log.warn("[异常订单] {} - orderId: {}", alertContent, order.getId());
|
|
|
|
|
+ try {
|
|
|
|
|
+ deviceAlertService.processAbnormalOrderAlert(deviceId, order.getId(), alertContent);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[异常订单] 触发告警失败 - orderId: {}", order.getId(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 更新订单信息
|
|
// 更新订单信息
|
|
@@ -446,6 +468,12 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void handleOrderCallback(Map<String, Object> params) {
|
|
public void handleOrderCallback(Map<String, Object> params) {
|
|
|
|
|
+ // 签名验证:确保订单回调来自哈哈平台
|
|
|
|
|
+ if (!validateSign(params)) {
|
|
|
|
|
+ log.warn("订单回调签名验证失败,忽略该通知");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
String orderId = extractParam(params, "order_id");
|
|
String orderId = extractParam(params, "order_id");
|
|
|
String deviceId = extractParam(params, "device_id");
|
|
String deviceId = extractParam(params, "device_id");
|
|
@@ -500,10 +528,86 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
public boolean validateSign(Map<String, Object> params) {
|
|
public boolean validateSign(Map<String, Object> params) {
|
|
|
String receivedSign = extractParam(params, "sign");
|
|
String receivedSign = extractParam(params, "sign");
|
|
|
if (receivedSign == null || receivedSign.isEmpty()) {
|
|
if (receivedSign == null || receivedSign.isEmpty()) {
|
|
|
- log.warn("签名参数为空");
|
|
|
|
|
|
|
+ log.warn("签名参数为空,回调来源可能未经验证");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- return true;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (appSecret == null || appSecret.isEmpty()) {
|
|
|
|
|
+ log.error("appSecret 未配置,无法验证签名");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 标准 HMAC-SHA256 签名验证算法:
|
|
|
|
|
+ // 1. 排除 sign、sign_type 等签名相关字段
|
|
|
|
|
+ // 2. 剩余参数按 key 字典序升序排列
|
|
|
|
|
+ // 3. 拼接为 key1=value1&key2=value2&...&keyN=valueN 格式
|
|
|
|
|
+ // 4. 使用 appSecret 作为密钥计算 HMAC-SHA256
|
|
|
|
|
+ // 5. 将结果转换为小写十六进制字符串,与 sign 参数比对
|
|
|
|
|
+ //
|
|
|
|
|
+ // 注意:如果哈哈平台的签名算法不同(如 MD5、拼接方式差异),
|
|
|
|
|
+ // 请根据平台文档调整下面的实现。
|
|
|
|
|
+
|
|
|
|
|
+ String signContent = buildSignContent(params);
|
|
|
|
|
+ String computedSign = hmacSha256(signContent, appSecret);
|
|
|
|
|
+
|
|
|
|
|
+ boolean isValid = computedSign.equalsIgnoreCase(receivedSign);
|
|
|
|
|
+ if (!isValid) {
|
|
|
|
|
+ log.warn("签名验证失败 - 计算签名: {}, 接收签名: {}, 签名内容: {}",
|
|
|
|
|
+ computedSign, receivedSign, signContent);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.debug("签名验证通过");
|
|
|
|
|
+ }
|
|
|
|
|
+ return isValid;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
|
|
|
+ log.error("HMAC-SHA256 算法不可用", e);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ } catch (InvalidKeyException e) {
|
|
|
|
|
+ log.error("签名密钥无效", e);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("签名验证异常", e);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建待签名字符串
|
|
|
|
|
+ * 将所有参数(排除 sign, sign_type)按 key 字典序排序,
|
|
|
|
|
+ * 拼接为 key=value 格式,以 & 连接
|
|
|
|
|
+ */
|
|
|
|
|
+ private String buildSignContent(Map<String, Object> params) {
|
|
|
|
|
+ return params.entrySet().stream()
|
|
|
|
|
+ .filter(e -> !"sign".equals(e.getKey()) && !"sign_type".equals(e.getKey()))
|
|
|
|
|
+ .filter(e -> e.getValue() != null)
|
|
|
|
|
+ .sorted(Map.Entry.comparingByKey())
|
|
|
|
|
+ .map(e -> e.getKey() + "=" + e.getValue().toString())
|
|
|
|
|
+ .collect(Collectors.joining("&"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * HMAC-SHA256 计算
|
|
|
|
|
+ */
|
|
|
|
|
+ private String hmacSha256(String data, String key)
|
|
|
|
|
+ throws NoSuchAlgorithmException, InvalidKeyException {
|
|
|
|
|
+ Mac mac = Mac.getInstance("HmacSHA256");
|
|
|
|
|
+ SecretKeySpec secretKeySpec = new SecretKeySpec(
|
|
|
|
|
+ key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
|
|
|
|
+ mac.init(secretKeySpec);
|
|
|
|
|
+ byte[] result = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ return bytesToHex(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 字节数组转小写十六进制字符串
|
|
|
|
|
+ */
|
|
|
|
|
+ private String bytesToHex(byte[] bytes) {
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ for (byte b : bytes) {
|
|
|
|
|
+ sb.append(String.format("%02x", b));
|
|
|
|
|
+ }
|
|
|
|
|
+ return sb.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void saveDeviceStatusToRedis(String deviceId, String activityId, String doorStatus, String openType, String outUserId) {
|
|
private void saveDeviceStatusToRedis(String deviceId, String activityId, String doorStatus, String openType, String outUserId) {
|
|
@@ -681,6 +785,17 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
order.setOrderType("OUT");
|
|
order.setOrderType("OUT");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // IN类型订单触发告警(基于订单名称判断)
|
|
|
|
|
+ if ("IN".equals(order.getOrderType())) {
|
|
|
|
|
+ String alertContent = String.format("订单回调识别为IN类型(异物放入),订单名称:%s", orderName);
|
|
|
|
|
+ log.warn("[异常订单] {} - orderId: {}, orderNo: {}", alertContent, order.getId(), orderId);
|
|
|
|
|
+ try {
|
|
|
|
|
+ deviceAlertService.processAbnormalOrderAlert(order.getDeviceId(), order.getId(), alertContent);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[异常订单] 触发告警失败 - orderId: {}", order.getId(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (orderDetail != null) {
|
|
if (orderDetail != null) {
|
|
|
order.setItems(orderDetail);
|
|
order.setItems(orderDetail);
|
|
|
}
|
|
}
|