RtMallSignMd5Utils.java 5.12 KB
package com.diligrp.cashier.mall.util;

import com.diligrp.cashier.shared.util.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.MessageDigest;
import java.util.*;

/**
 * @ClassName SignMd5Utils.java
 * @author dengwei
 * @version 1.0.0
 * @Description
 * 签名算法
 * 参考大润发定义:https://shopex.yuque.com/hl0rrx/vlp0m4/kmcazgnimg28d8ih?singleDoc#
 */
public class RtMallSignMd5Utils {
    private static final Logger log = LoggerFactory.getLogger(RtMallSignMd5Utils.class);

    /**
     * 生成签名
     *
     */
    public static String generateSign(Object data, String appSecret) {
        Map<String, Object> params = JsonUtils.convertValueV2(data, new TypeReference<>() {
        });
        // 1. 过滤 null,转换 boolean,递归规范化并排序
        String normalized = normalizeAndSort(params);
        log.info("normalize: {}", normalized);
        // 2. 拼接私钥
        String signSource = appSecret + normalized + appSecret;
        log.info("signSource: {}", signSource);
        // 3. MD5 + 大写
        String sign = md5ToUpper(signSource);
        log.info("sign: {}", sign);
        return sign;
    }

    /**
     * 递归返回拼接字符串
     */
    @SuppressWarnings("unchecked")
    private static String normalizeAndSort(Object obj) {
        switch (obj) {
            case null -> {
                return "";
            }
            case Map map1 -> {
                Map<String, Object> map = (Map<String, Object>) obj;
                // 过滤 null,并转换 boolean
                Map<String, Object> filtered = new LinkedHashMap<>();
                for (Map.Entry<String, Object> entry : map.entrySet()) {
                    if (entry.getValue() == null) {
                        continue;
                    }
                    Object val = convertBoolean(entry.getValue());
                    filtered.put(entry.getKey(), val);
                }
                // 按 key 字典序排序
                List<String> sortedKeys = new ArrayList<>(filtered.keySet());
                Collections.sort(sortedKeys);

                StringBuilder sb = new StringBuilder();
                for (String key : sortedKeys) {
                    sb.append(key).append(normalizeAndSort(filtered.get(key)));
                }
                return sb.toString();
            }
            case List list1 -> {
                List<Object> list = (List<Object>) obj;
                // 转为 Map<String, Object>,key 为索引字符串
                Map<String, Object> indexMap = new LinkedHashMap<>();
                for (int i = 0; i < list.size(); i++) {
                    indexMap.put(String.valueOf(i), list.get(i));
                }
                // 递归处理(会自动过滤 null、转换 boolean、排序 key)
                return normalizeAndSort(indexMap);
            }
            case Object[] array -> {
                // 处理数组类型,转换为 Map 并递归处理
                Map<String, Object> arrayMap = new LinkedHashMap<>();
                for (int i = 0; i < array.length; i++) {
                    arrayMap.put(String.valueOf(i), array[i]);
                }
                return normalizeAndSort(arrayMap);
            }
            default -> {
            }
        }
        return obj.toString();
    }

    /**
     * 转换 Boolean 为 "1"/"0"
     */
    private static Object convertBoolean(Object obj) {
        if (obj instanceof Boolean) {
            return (Boolean) obj ? "1" : "0";
        }
        return obj;
    }

    /**
     * MD5 并转大写
     */
    private static String md5ToUpper(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString().toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException("MD5 failed", e);
        }
    }

    public static void main(String[] args) {
        // 构建顶层参数 Map
        Map<String, Object> params = new HashMap<>();

        params.put("app_key", "221zzcwxee30nucj0rx1");
        params.put("timestamp", 1734351194L); // 使用 Long 避免溢出
        params.put("nonce_str", "cvwEv8JRNzHd3eaV");
        params.put("order_id", "1234567890"); // order_id 可以是字符串或数字,这里按字符串处理更安全

        // 构建 item_list
        List<Map<String, Object>> itemList = new ArrayList<>();

        for (int i = 1; i <= 11; i++) {
            Map<String, Object> subOrder = new HashMap<>();
            subOrder.put("sub_order_id", i);
            subOrder.put("price", 100);
            itemList.add(subOrder);
        }
        params.put("item_list", itemList);

        // 打印验证(可选)
        log.info("Params: {}", JsonUtils.toJsonString(params));

        String sign = generateSign(params, "ho7ygrsrwged4zwhwpbadtdgzugmulez");
        log.info("Sign: {}", sign);
    }
}