81 lines
2.9 KiB
Java
81 lines
2.9 KiB
Java
package com.baiyue.common.event;
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
|
|
|
/**
|
|
* 将 JSON 负载转换为稳定字节序列,避免对象字段顺序不同导致摘要不一致。
|
|
*/
|
|
public final class EventPayloadCanonicalizer {
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
|
|
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
|
|
|
|
private EventPayloadCanonicalizer() {
|
|
}
|
|
|
|
/**
|
|
* 递归排序对象字段并输出 UTF-8 JSON 字节。
|
|
*/
|
|
public static byte[] canonicalize(JsonNode payload) {
|
|
if (payload == null || payload.isNull()) {
|
|
throw new IllegalArgumentException("事件负载不能为空");
|
|
}
|
|
return write(sort(payload));
|
|
}
|
|
|
|
/** 仅用于接收规范化数字规则上线前已经生成的历史摘要。 */
|
|
public static byte[] canonicalizeLegacy(JsonNode payload) {
|
|
if (payload == null || payload.isNull()) {
|
|
throw new IllegalArgumentException("事件负载不能为空");
|
|
}
|
|
return write(sortLegacy(payload));
|
|
}
|
|
|
|
private static byte[] write(JsonNode payload) {
|
|
try {
|
|
return OBJECT_MAPPER.writeValueAsBytes(payload);
|
|
} catch (JsonProcessingException exception) {
|
|
throw new IllegalArgumentException("事件负载无法规范化", exception);
|
|
}
|
|
}
|
|
|
|
private static JsonNode sort(JsonNode node) {
|
|
if (node.isObject()) {
|
|
var names = new java.util.ArrayList<String>();
|
|
node.fieldNames().forEachRemaining(names::add);
|
|
names.sort(String::compareTo);
|
|
var result = OBJECT_MAPPER.createObjectNode();
|
|
names.forEach(name -> result.set(name, sort(node.get(name))));
|
|
return result;
|
|
}
|
|
if (node.isArray()) {
|
|
var result = OBJECT_MAPPER.createArrayNode();
|
|
node.forEach(child -> result.add(sort(child)));
|
|
return result;
|
|
}
|
|
if (node.isNumber()) {
|
|
return com.fasterxml.jackson.databind.node.DecimalNode.valueOf(node.decimalValue().stripTrailingZeros());
|
|
}
|
|
return node;
|
|
}
|
|
|
|
private static JsonNode sortLegacy(JsonNode node) {
|
|
if (node.isObject()) {
|
|
var names = new java.util.ArrayList<String>();
|
|
node.fieldNames().forEachRemaining(names::add);
|
|
names.sort(String::compareTo);
|
|
var result = OBJECT_MAPPER.createObjectNode();
|
|
names.forEach(name -> result.set(name, sortLegacy(node.get(name))));
|
|
return result;
|
|
}
|
|
if (node.isArray()) {
|
|
var result = OBJECT_MAPPER.createArrayNode();
|
|
node.forEach(child -> result.add(sortLegacy(child)));
|
|
return result;
|
|
}
|
|
return node;
|
|
}
|
|
}
|