From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master
---
jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/AuditFieldDiffViewService.java | 485 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 485 insertions(+), 0 deletions(-)
diff --git a/jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/AuditFieldDiffViewService.java b/jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/AuditFieldDiffViewService.java
new file mode 100644
index 0000000..fa124e1
--- /dev/null
+++ b/jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/AuditFieldDiffViewService.java
@@ -0,0 +1,485 @@
+package jnpf.bizcommon.audit.service;
+
+import com.alibaba.fastjson.JSON;
+import jnpf.bizcommon.audit.entity.AuditEventEntity;
+import jnpf.audit.diff.AuditFieldDiff;
+import jnpf.bizcommon.audit.entity.model.AuditFieldDiffViewVO;
+import jnpf.bizcommon.audit.service.support.AuditQueryResponseSanitizer;
+import jnpf.bizcommon.audit.service.value.AuditFieldValueContext;
+import jnpf.bizcommon.audit.service.value.AuditFieldValueResolver;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/** 灏嗗吋瀹瑰瓧绗︿覆 fieldDiffs 瑁呴厤鎴愮粨鏋勫寲銆佸彲灞曠ず涓斿彲鑴辨晱鐨勫瓧娈靛彉鏇淬�� */
+@Service
+@Slf4j
+public class AuditFieldDiffViewService {
+
+ private final List<AuditFieldValueResolver> resolvers;
+ private final ZoneId businessZone;
+
+ @Autowired
+ public AuditFieldDiffViewService(
+ List<AuditFieldValueResolver> resolvers,
+ @Value("${audit.query.business-time-zone:Asia/Shanghai}") String businessTimeZone) {
+ this.resolvers = resolvers == null ? Collections.emptyList() : new ArrayList<>(resolvers);
+ this.businessZone = parseZone(businessTimeZone);
+ }
+
+ /**
+ * sanitizedFieldDiffs 宸茬敱鏌ヨ鑴辨晱鍣ㄨ繃婊ゆ棤鎰忎箟椤瑰苟澶勭悊鍑瘉寮曠敤锛屽師濮嬩簨浠朵笉浼氳淇敼銆�
+ */
+ @SuppressWarnings("unchecked")
+ public List<AuditFieldDiffViewVO> build(AuditEventEntity event,
+ String sanitizedFieldDiffs,
+ Map<String, AuditFieldDiff.FieldMeta> currentMetadata) {
+ if (sanitizedFieldDiffs == null || sanitizedFieldDiffs.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+ Object parsed;
+ try {
+ parsed = JSON.parse(sanitizedFieldDiffs);
+ } catch (Throwable ignored) {
+ return Collections.emptyList();
+ }
+ if (!(parsed instanceof List)) {
+ return Collections.emptyList();
+ }
+
+ List<AuditFieldDiffViewVO> views = new ArrayList<>();
+ Map<String, String> dictionaryTypes = new LinkedHashMap<>();
+ for (Object raw : (List<?>) parsed) {
+ if (!(raw instanceof Map)) {
+ continue;
+ }
+ Map<String, Object> item = (Map<String, Object>) raw;
+ AuditFieldDiffViewVO view = toView(item,
+ currentMetadata, event == null ? null : event.getActionCode());
+ if (view != null) {
+ views.add(view);
+ AuditFieldDiff.FieldMeta meta = metadata(currentMetadata, view.getField());
+ String dictionaryType = firstText(item.get("dictionaryType"),
+ meta == null ? null : meta.dictionaryType());
+ if (dictionaryType != null) {
+ dictionaryTypes.put(view.getField().toLowerCase(Locale.ROOT), dictionaryType);
+ }
+ }
+ }
+
+ AuditFieldValueContext context = new AuditFieldValueContext(
+ event == null ? null : event.getTargetTable(),
+ event == null ? null : event.getTenantId(),
+ event == null ? null : event.getEventTime(),
+ dictionaryTypes);
+ List<AuditFieldDiffViewVO> resolvedViews = views;
+ for (AuditFieldValueResolver resolver : resolvers) {
+ if (resolver != null) {
+ try {
+ if (!resolver.supports(context.getTargetTable())) {
+ continue;
+ }
+ List<AuditFieldDiffViewVO> candidate = copyViews(resolvedViews);
+ resolver.resolve(context, candidate);
+ resolvedViews = candidate;
+ } catch (RuntimeException ex) {
+ // 瑙f瀽澶辫触鍙兘闄嶇骇褰撳墠 resolver锛屾棩蹇椾笉鎼哄甫瀛楁鍊兼垨璇︽儏姝f枃銆�
+ log.warn("[AUDIT-QUERY] 瀛楁鍊艰В鏋愬櫒澶辫触 resolver={} err={}",
+ resolver.getClass().getSimpleName(), ex.getClass().getSimpleName());
+ }
+ }
+ }
+ for (AuditFieldDiffViewVO view : resolvedViews) {
+ finishDisplay(view);
+ }
+ return resolvedViews;
+ }
+
+ /**
+ * 涓烘柊浜嬩欢鍥哄寲鍙彲闈犺В鏋愮殑绫诲瀷鍜屽睍绀哄�硷紝鍚屾椂淇濈暀鏃� field_diffs 鐨� type/jnpfKey 绛夐敭銆�
+ * 瑙f瀽澶辫触鏃跺師鏍疯繑鍥烇紝涓嶈兘鍥犱负灞曠ず澧炲己閫犳垚 Layer 0 浜嬩欢涓㈠け銆�
+ */
+ @SuppressWarnings("unchecked")
+ public String enrichForPersistence(AuditEventEntity event,
+ String fieldDiffs,
+ Map<String, AuditFieldDiff.FieldMeta> metadata) {
+ if (fieldDiffs == null || fieldDiffs.trim().isEmpty()) {
+ return fieldDiffs;
+ }
+ try {
+ Object parsed = JSON.parse(fieldDiffs);
+ if (!(parsed instanceof List)) {
+ return fieldDiffs;
+ }
+ List<AuditFieldDiffViewVO> views = build(event, fieldDiffs, metadata);
+ Map<String, AuditFieldDiffViewVO> byField = new LinkedHashMap<>();
+ for (AuditFieldDiffViewVO view : views) {
+ if (view.getField() != null) {
+ byField.put(view.getField().toLowerCase(Locale.ROOT), view);
+ }
+ }
+ for (Object raw : (List<?>) parsed) {
+ if (!(raw instanceof Map)) {
+ continue;
+ }
+ Map<String, Object> item = (Map<String, Object>) raw;
+ String field = text(item.get("field"));
+ AuditFieldDiffViewVO view = field == null
+ ? null : byField.get(field.toLowerCase(Locale.ROOT));
+ if (view == null) {
+ continue;
+ }
+ putIfMissing(item, "valueType", view.getValueType());
+ putIfMissing(item, "componentType", view.getComponentType());
+ if (Boolean.TRUE.equals(view.getMasked())) {
+ item.put("masked", true);
+ item.put("valueType", "masked");
+ item.put("componentType", "sign");
+ if (item.containsKey("oldData")) {
+ item.put("oldData", AuditFieldDiff.MASKED);
+ }
+ if (item.containsKey("newData")) {
+ item.put("newData", AuditFieldDiff.MASKED);
+ }
+ item.remove("oldDisplay");
+ item.remove("newDisplay");
+ continue;
+ }
+ if (Boolean.TRUE.equals(view.getTechnical())) {
+ item.put("technical", true);
+ }
+ if (snapshotDisplayType(view.getValueType())) {
+ putDisplayIfResolved(item, "oldDisplay", view.getOldDisplay());
+ putDisplayIfResolved(item, "newDisplay", view.getNewDisplay());
+ }
+ }
+ return JSON.toJSONString(parsed);
+ } catch (Throwable ex) {
+ log.warn("[AUDIT-L0] 瀛楁灞曠ず蹇収鍥哄寲澶辫触 err={}", ex.getClass().getSimpleName());
+ return fieldDiffs;
+ }
+ }
+
+ private AuditFieldDiffViewVO toView(Map<String, Object> item,
+ Map<String, AuditFieldDiff.FieldMeta> currentMetadata,
+ String actionCode) {
+ String field = text(item.get("field"));
+ if (field == null) {
+ return null;
+ }
+ AuditFieldDiff.FieldMeta meta = metadata(currentMetadata, field);
+ boolean credential = AuditQueryResponseSanitizer.isCredentialReference(field, item, meta);
+ boolean masked = credential
+ || AuditFieldDiff.MASKED.equals(text(item.get("oldData")))
+ || AuditFieldDiff.MASKED.equals(text(item.get("newData")));
+
+ AuditFieldDiffViewVO view = new AuditFieldDiffViewVO();
+ view.setField(field);
+ view.setTargetTable(text(item.get("targetTable")));
+ view.setFieldName(credential ? "鐢靛瓙绛惧悕" : fieldName(item, meta));
+ view.setChangeType(changeType(item, actionCode, masked));
+ String componentType = credential ? "sign" : firstText(item.get("componentType"),
+ item.get("jnpfKey"), meta == null ? null : meta.componentType());
+ String valueType = masked ? "masked" : firstText(item.get("valueType"),
+ meta == null ? null : meta.valueType());
+ if (valueType == null && isTemporalComponent(componentType)) {
+ valueType = inferValueType(componentType);
+ }
+ view.setValueType(valueType);
+ view.setComponentType(componentType);
+ view.setJnpfKey(firstText(item.get("jnpfKey"), componentType));
+ view.setNameModified(bool(item.get("nameModified")));
+ view.setType(integer(item.get("type")));
+ view.setChidData(mapList(item.get("chidData")));
+ view.setChidField(mapList(item.get("chidField")));
+ view.setMasked(masked);
+ view.setTechnical(bool(item.get("technical")));
+ if (masked) {
+ view.setOldData(null);
+ view.setNewData(null);
+ view.setOldDisplay(null);
+ view.setNewDisplay(null);
+ return view;
+ }
+
+ String oldData = rawText(item.get("oldData"));
+ String newData = rawText(item.get("newData"));
+ view.setOldData(oldData);
+ view.setNewData(newData);
+ view.setOldDisplay(firstTextPreserveEmpty(item.get("oldDisplay"), optionDisplay(meta, oldData),
+ temporalDisplay(view.getValueType(), item, meta, oldData)));
+ view.setNewDisplay(firstTextPreserveEmpty(item.get("newDisplay"), optionDisplay(meta, newData),
+ temporalDisplay(view.getValueType(), item, meta, newData)));
+ return view;
+ }
+
+ private void finishDisplay(AuditFieldDiffViewVO view) {
+ if (Boolean.TRUE.equals(view.getMasked())) {
+ return;
+ }
+ if (view.getValueType() == null) {
+ view.setValueType(inferValueType(view.getComponentType()));
+ }
+ if (view.getComponentType() == null) {
+ view.setComponentType("input");
+ }
+ view.setOldDisplay(fallbackDisplay(view.getValueType(), view.getOldData(), view.getOldDisplay()));
+ view.setNewDisplay(fallbackDisplay(view.getValueType(), view.getNewData(), view.getNewDisplay()));
+ if (view.getTechnical() == null) {
+ view.setTechnical(Boolean.FALSE);
+ }
+ if (view.getMasked() == null) {
+ view.setMasked(Boolean.FALSE);
+ }
+ }
+
+ private String fallbackDisplay(String valueType, String raw, String display) {
+ if (display != null) {
+ return display;
+ }
+ if (raw == null || raw.trim().isEmpty()) {
+ return "";
+ }
+ if ("reference".equals(valueType) || "multiReference".equals(valueType)
+ || "enum".equals(valueType) || "multiEnum".equals(valueType)) {
+ return "鍚嶇О鏆備笉鍙敤";
+ }
+ return raw;
+ }
+
+ private String temporalDisplay(String valueType, Map<String, Object> item,
+ AuditFieldDiff.FieldMeta meta, String raw) {
+ if (!("date".equals(valueType) || "datetime".equals(valueType))
+ || raw == null || raw.trim().isEmpty()) {
+ return null;
+ }
+ try {
+ long epochMillis = Long.parseLong(raw.trim());
+ String pattern = firstText(item.get("format"), meta == null ? null : meta.format(),
+ "date".equals(valueType) ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm:ss");
+ return DateTimeFormatter.ofPattern(pattern, Locale.ROOT)
+ .withZone(businessZone).format(Instant.ofEpochMilli(epochMillis));
+ } catch (RuntimeException ignored) {
+ return null;
+ }
+ }
+
+ private static String optionDisplay(AuditFieldDiff.FieldMeta meta, String raw) {
+ return meta == null ? null : meta.optionDisplay(raw);
+ }
+
+ private static boolean snapshotDisplayType(String valueType) {
+ return "enum".equals(valueType) || "multiEnum".equals(valueType)
+ || "reference".equals(valueType) || "multiReference".equals(valueType)
+ || "date".equals(valueType) || "datetime".equals(valueType);
+ }
+
+ private static void putIfMissing(Map<String, Object> item, String key, String value) {
+ if (!item.containsKey(key) && value != null) {
+ item.put(key, value);
+ }
+ }
+
+ private static void putDisplayIfResolved(Map<String, Object> item, String key, String display) {
+ if (!item.containsKey(key) && display != null && !display.trim().isEmpty()
+ && !"鍚嶇О鏆備笉鍙敤".equals(display)) {
+ item.put(key, display);
+ }
+ }
+
+ private static AuditFieldDiff.FieldMeta metadata(
+ Map<String, AuditFieldDiff.FieldMeta> metadata, String field) {
+ if (metadata == null || field == null) {
+ return null;
+ }
+ AuditFieldDiff.FieldMeta exact = metadata.get(field);
+ if (exact != null) {
+ return exact;
+ }
+ AuditFieldDiff.FieldMeta normalized = metadata.get(field.toLowerCase(Locale.ROOT));
+ if (normalized != null) {
+ return normalized;
+ }
+ StringBuilder snake = new StringBuilder(field.length() + 8);
+ for (int i = 0; i < field.length(); i++) {
+ char ch = field.charAt(i);
+ if (Character.isUpperCase(ch)) {
+ snake.append('_').append(Character.toLowerCase(ch));
+ } else {
+ snake.append(Character.toLowerCase(ch));
+ }
+ }
+ return metadata.get(snake.toString());
+ }
+
+ private static String fieldName(Map<String, Object> item, AuditFieldDiff.FieldMeta meta) {
+ String name = text(item.get("fieldName"));
+ if (name != null) {
+ return name;
+ }
+ return meta != null && meta.label() != null && !meta.label().trim().isEmpty()
+ ? meta.label() : text(item.get("field"));
+ }
+
+ private static String changeType(Map<String, Object> item, String actionCode, boolean masked) {
+ if (masked) {
+ return "updated";
+ }
+ String explicit = text(item.get("changeType"));
+ if ("created".equals(explicit) || "updated".equals(explicit) || "deleted".equals(explicit)) {
+ return explicit;
+ }
+ if (actionCode != null && actionCode.toUpperCase(Locale.ROOT).contains("DELETE")) {
+ return "deleted";
+ }
+ Object type = item.get("type");
+ return type != null && "0".equals(String.valueOf(type)) ? "created" : "updated";
+ }
+
+ private static String inferValueType(String component) {
+ if (component == null) {
+ return "text";
+ }
+ String key = component.toLowerCase(Locale.ROOT);
+ if (key.contains("time")) {
+ return "datetime";
+ }
+ if (key.contains("date")) {
+ return "date";
+ }
+ if (key.contains("number") || "slider".equals(key) || "rate".equals(key)) {
+ return "number";
+ }
+ if ("switch".equals(key)) {
+ return "boolean";
+ }
+ if ("checkbox".equals(key)) {
+ return "multiEnum";
+ }
+ if (key.contains("select") || "radio".equals(key)) {
+ return "enum";
+ }
+ if ("table".equals(key) || key.contains("upload")) {
+ return "json";
+ }
+ return "text";
+ }
+
+ private static boolean isTemporalComponent(String component) {
+ if (component == null) {
+ return false;
+ }
+ String key = component.toLowerCase(Locale.ROOT);
+ return key.contains("date") || key.contains("time");
+ }
+
+ private static List<AuditFieldDiffViewVO> copyViews(List<AuditFieldDiffViewVO> source) {
+ List<AuditFieldDiffViewVO> copy = new ArrayList<>(source.size());
+ for (AuditFieldDiffViewVO value : source) {
+ AuditFieldDiffViewVO item = new AuditFieldDiffViewVO();
+ item.setField(value.getField());
+ item.setTargetTable(value.getTargetTable());
+ item.setFieldName(value.getFieldName());
+ item.setChangeType(value.getChangeType());
+ item.setValueType(value.getValueType());
+ item.setComponentType(value.getComponentType());
+ item.setJnpfKey(value.getJnpfKey());
+ item.setNameModified(value.getNameModified());
+ item.setType(value.getType());
+ item.setChidData(value.getChidData());
+ item.setChidField(value.getChidField());
+ item.setOldData(value.getOldData());
+ item.setNewData(value.getNewData());
+ item.setOldDisplay(value.getOldDisplay());
+ item.setNewDisplay(value.getNewDisplay());
+ item.setMasked(value.getMasked());
+ item.setTechnical(value.getTechnical());
+ copy.add(item);
+ }
+ return copy;
+ }
+
+ private static ZoneId parseZone(String zone) {
+ try {
+ return ZoneId.of(zone == null || zone.trim().isEmpty() ? "Asia/Shanghai" : zone.trim());
+ } catch (RuntimeException ignored) {
+ return ZoneId.of("Asia/Shanghai");
+ }
+ }
+
+ private static boolean bool(Object value) {
+ return value instanceof Boolean ? (Boolean) value
+ : value != null && "true".equalsIgnoreCase(String.valueOf(value));
+ }
+
+ private static Integer integer(Object value) {
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ }
+ try {
+ return value == null ? null : Integer.valueOf(String.valueOf(value));
+ } catch (NumberFormatException ignored) {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<Map<String, Object>> mapList(Object value) {
+ if (!(value instanceof List)) {
+ return null;
+ }
+ List<Map<String, Object>> result = new ArrayList<>();
+ for (Object item : (List<?>) value) {
+ if (item instanceof Map) {
+ result.add(new LinkedHashMap<>((Map<String, Object>) item));
+ }
+ }
+ return result.isEmpty() ? null : result;
+ }
+
+ private static String rawText(Object value) {
+ return value == null ? "" : String.valueOf(value);
+ }
+
+ private static String text(Object value) {
+ if (value == null) {
+ return null;
+ }
+ String result = String.valueOf(value).trim();
+ return result.isEmpty() ? null : result;
+ }
+
+ private static String firstText(Object... values) {
+ if (values != null) {
+ for (Object value : values) {
+ String result = text(value);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String firstTextPreserveEmpty(Object... values) {
+ if (values != null) {
+ for (Object value : values) {
+ if (value != null) {
+ return String.valueOf(value);
+ }
+ }
+ }
+ return null;
+ }
+}
--
Gitblit v1.8.0