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/impl/AuditQueryServiceImpl.java |  353 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 353 insertions(+), 0 deletions(-)

diff --git a/jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/impl/AuditQueryServiceImpl.java b/jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/impl/AuditQueryServiceImpl.java
new file mode 100644
index 0000000..1c193f3
--- /dev/null
+++ b/jnpf-biz-common/jnpf-biz-common-audit/src/main/java/jnpf/bizcommon/audit/service/impl/AuditQueryServiceImpl.java
@@ -0,0 +1,353 @@
+package jnpf.bizcommon.audit.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import jnpf.audit.AuditConsts;
+import jnpf.bizcommon.audit.entity.AuditEventEntity;
+import jnpf.bizcommon.audit.entity.model.AuditEventDetailVO;
+import jnpf.bizcommon.audit.entity.model.AuditEventListVO;
+import jnpf.bizcommon.audit.entity.model.AuditOperationGroupRow;
+import jnpf.bizcommon.audit.entity.model.AuditPageQuery;
+import jnpf.bizcommon.audit.mapper.AuditEventMapper;
+import jnpf.bizcommon.audit.mapper.AuditQueryMapper;
+import jnpf.bizcommon.audit.service.AuditFieldDiffViewService;
+import jnpf.bizcommon.audit.service.AuditFormRegistry;
+import jnpf.bizcommon.audit.service.AuditQueryService;
+import jnpf.bizcommon.audit.service.support.AuditQueryResponseSanitizer;
+import jnpf.bizcommon.audit.service.support.AuditEventCategories;
+import jnpf.base.vo.PageListVO;
+import jnpf.base.vo.PaginationVO;
+import jnpf.exception.DataException;
+import jnpf.util.JsonUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/** 瀹¤浜嬩欢鏌ヨ鏈嶅姟锛涘啓鍏ヤ晶瀹屾垚鐗╃悊鍚堝苟锛屾煡璇晶浠呮寜 operationId 缁勭粐灞曠ず鍏崇郴銆� */
+@Service
+public class AuditQueryServiceImpl implements AuditQueryService {
+
+    /** timeline 鍏ㄩ噺涓婇檺锛坰pec锛歀IMIT 500锛� */
+    private static final int TIMELINE_LIMIT = 500;
+    /** 鍒楄〃鍙笅鍙戝墠 3 鏉″瓙琛ㄦ爣棰� + 鎬绘暟鈥斺�斾繚鎸併�屽垪琛ㄤ笉鍚� extra 姝f枃銆嶇殑鏁忔劅闈㈢害瀹氥�� */
+    private static final int LIST_SUB_TITLE_LIMIT = 3;
+    /** 鍒嗛〉澶у皬涓婇檺锛圫tep 2 濂戠害锛氬己鍒� pageSize<=100锛� */
+    private static final long MAX_PAGE_SIZE = 100L;
+    private static final long DEFAULT_PAGE_SIZE = 20L;
+
+    @Autowired
+    private AuditQueryMapper auditQueryMapper;
+
+    @Autowired
+    private AuditEventMapper auditEventMapper;
+
+    @Autowired
+    private AuditFormRegistry auditFormRegistry;
+
+    @Autowired
+    private AuditFieldDiffViewService auditFieldDiffViewService;
+
+    @Override
+    public PageListVO<AuditEventListVO> page(AuditPageQuery query) {
+        long pageSize = query.getPageSize();
+        if (pageSize <= 0) {
+            pageSize = DEFAULT_PAGE_SIZE;
+        } else if (pageSize > MAX_PAGE_SIZE) {
+            pageSize = MAX_PAGE_SIZE;
+        }
+        long currentPage = query.getCurrentPage();
+        if (currentPage <= 0) {
+            currentPage = 1L;
+        }
+        // Codex 鎵规涓塧 Minor#3锛歄FFSET 婧㈠嚭闃叉姢鈥斺�攃urrentPage 浼犺秴澶у�硷紙濡� Long.MAX_VALUE锛夋椂
+        // (currentPage-1)*pageSize 浼� long 涔樻硶婧㈠嚭鎴愯礋鏁帮紝鎷艰繘 SQL LIMIT/OFFSET 璇箟鏈畾涔夈��
+        // Math.multiplyExact 鏄惧紡妫�娴嬫孩鍑猴紝鍛戒腑鍒欓檷绾т负绌洪〉锛坱otal 浠嶆寜鏉′欢姝e父杩斿洖锛屼笉鏌ユ槑缁嗛〉锛夈��
+        long offset;
+        boolean offsetOverflow;
+        try {
+            offset = Math.multiplyExact(currentPage - 1, pageSize);
+            offsetOverflow = false;
+        } catch (ArithmeticException ex) {
+            offset = 0L;
+            offsetOverflow = true;
+        }
+
+        QueryWrapper<AuditEventEntity> ew = buildWrapper(query);
+        long total = auditQueryMapper.countOperations(ew);
+        List<AuditEventListVO> list;
+        if (offsetOverflow) {
+            list = Collections.emptyList();
+        } else {
+            List<AuditOperationGroupRow> groups = auditQueryMapper.selectOperationPage(ew, offset, pageSize);
+            list = buildOperationRows(groups);
+        }
+
+        // 鍥炲啓鐢熸晥鍚庣殑鍒嗛〉鍙傛暟渚涜繑鍥炲寘瑁呰鍙栵紙瀵圭収 LimsBizLogController锛歅agination 鎵胯浇 total锛�
+        // JsonUtil.getJsonToBean 绐勫寲鎴� PaginationVO鈥斺�斿悓涓�鎯緥锛屼粎鎸埌 Service 灞備互璐村悎鏈换鍔�
+        // 瀛楅潰鎵ц鐨� Controller 绛惧悕锛�
+        query.setCurrentPage(currentPage);
+        query.setPageSize(pageSize);
+        query.setTotal(total);
+
+        PageListVO<AuditEventListVO> vo = new PageListVO<>();
+        vo.setList(list);
+        vo.setPagination(JsonUtil.getJsonToBean(query, PaginationVO.class));
+        return vo;
+    }
+
+    @Override
+    public AuditEventDetailVO detail(Long id) {
+        AuditEventEntity entity = auditEventMapper.selectById(id);
+        if (entity == null) {
+            throw new DataException("瀹¤浜嬩欢涓嶅瓨鍦細id=" + id);
+        }
+        return toDetailVo(entity);
+    }
+
+    @Override
+    public List<AuditEventListVO> timeline(String targetId) {
+        List<AuditOperationGroupRow> groups = groupRows(
+                auditQueryMapper.selectTimeline(targetId, TIMELINE_LIMIT));
+        // target_id 鍙睘浜庝笟鍔$洰鏍囪锛涚鍚嶇瓑鍏宠仈浜嬩欢浣跨敤鑷繁鐨� target_id锛岄渶鎸� operation_id 琛ユ煡銆�
+        return buildOperationRows(groups);
+    }
+
+    private List<AuditEventListVO> buildOperationRows(List<AuditOperationGroupRow> groups) {
+        if (groups.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> gids = new ArrayList<>(groups.size());
+        for (AuditOperationGroupRow group : groups) {
+            gids.add(group.getGid());
+        }
+        return buildOperationRows(groups, auditQueryMapper.selectByOperationGids(gids));
+    }
+
+    /** 灏嗗凡鐗╃悊鍚堝苟鐨勪簨浠舵寕鍒� BUSINESS 涓昏锛涜繖閲屼笉寰楀啀娆$粍鍚� fieldDiffs銆� */
+    private List<AuditEventListVO> buildOperationRows(List<AuditOperationGroupRow> groups,
+                                                      List<AuditEventEntity> events) {
+        Map<String, List<AuditEventEntity>> eventsByGid = new LinkedHashMap<>();
+        for (AuditEventEntity event : events) {
+            String gid = operationGid(event);
+            eventsByGid.computeIfAbsent(gid, ignored -> new ArrayList<>()).add(event);
+        }
+
+        List<AuditEventListVO> result = new ArrayList<>(groups.size());
+        for (AuditOperationGroupRow group : groups) {
+            List<AuditEventEntity> related = eventsByGid.get(group.getGid());
+            if (related == null) {
+                continue;
+            }
+            AuditEventEntity business = null;
+            List<AuditEventListVO> signatures = new ArrayList<>(2);
+            for (AuditEventEntity event : related) {
+                if (AuditEventCategories.BUSINESS.equals(event.getEventCategory())) {
+                    business = event;
+                } else if (AuditEventCategories.PRIMARY_SIGN.equals(event.getEventCategory())
+                        || AuditEventCategories.REVIEW_SIGN.equals(event.getEventCategory())) {
+                    signatures.add(toListVo(event));
+                }
+            }
+            if (business == null) {
+                continue;
+            }
+            AuditEventListVO main = toListVo(business);
+            main.setSubEvents(signatures);
+            main.setSigned(!signatures.isEmpty());
+            result.add(main);
+        }
+        return result;
+    }
+
+    private List<AuditOperationGroupRow> groupRows(List<AuditEventEntity> events) {
+        Map<String, AuditOperationGroupRow> groups = new LinkedHashMap<>();
+        for (AuditEventEntity event : events) {
+            String gid = operationGid(event);
+            AuditOperationGroupRow group = groups.get(gid);
+            if (group == null) {
+                group = new AuditOperationGroupRow();
+                group.setGid(gid);
+                group.setGrpTime(event.getEventTime());
+                groups.put(gid, group);
+            }
+        }
+        return new ArrayList<>(groups.values());
+    }
+
+    private String operationGid(AuditEventEntity event) {
+        return StringUtils.hasText(event.getOperationId()) ? event.getOperationId() : event.getClientEventId();
+    }
+
+    private QueryWrapper<AuditEventEntity> buildWrapper(AuditPageQuery query) {
+        QueryWrapper<AuditEventEntity> ew = new QueryWrapper<>();
+        if (query.getStartTime() != null) {
+            ew.ge("event_time", query.getStartTime());
+        }
+        if (query.getEndTime() != null) {
+            ew.le("event_time", query.getEndTime());
+        }
+        if (StringUtils.hasText(query.getOperatorName())) {
+            ew.like("operator_name", query.getOperatorName());
+        }
+        if (StringUtils.hasText(query.getAppName())) {
+            ew.eq("app_name", query.getAppName());
+        }
+        // Codex 鎵规涓塧 Important#1锛歜iz_module 鏄敤鎴疯瑙掔殑涓氬姟搴旂敤绛涢�夛紙app_name 灞� 0 鍥哄畾涓� jnpf-visualdev锛�
+        if (StringUtils.hasText(query.getBizModule())) {
+            ew.eq("biz_module", query.getBizModule());
+        }
+        if (StringUtils.hasText(query.getEventType())) {
+            ew.eq("event_type", query.getEventType());
+        }
+        if (StringUtils.hasText(query.getActionCode())) {
+            ew.eq("action_code", query.getActionCode());
+        }
+        if (StringUtils.hasText(query.getTargetTable())) {
+            ew.eq("target_table", query.getTargetTable());
+        }
+        if (StringUtils.hasText(query.getBizCode())) {
+            ew.eq("biz_code", query.getBizCode());
+        }
+        if (StringUtils.hasText(query.getBizType())) {
+            ew.eq("biz_type", query.getBizType());
+        }
+        if (StringUtils.hasText(query.getKeyword())) {
+            ew.like("field_diffs", query.getKeyword());
+        }
+        return ew;
+    }
+
+    private AuditEventListVO toListVo(AuditEventEntity e) {
+        AuditEventListVO vo = new AuditEventListVO();
+        vo.setId(e.getId());
+        vo.setOperationId(e.getOperationId());
+        vo.setClientEventId(e.getClientEventId());
+        vo.setEventTime(e.getEventTime());
+        vo.setTenantId(e.getTenantId());
+        vo.setOperatorId(e.getOperatorId());
+        vo.setOperatorName(e.getOperatorName());
+        vo.setOperatorOrgId(e.getOperatorOrgId());
+        vo.setAppName(e.getAppName());
+        vo.setBizModule(e.getBizModule());
+        vo.setIp(e.getIp());
+        vo.setIpRegion(e.getIpRegion());
+        vo.setBrowser(e.getBrowser());
+        vo.setOs(e.getOs());
+        vo.setRequestUri(e.getRequestUri());
+        vo.setEventType(e.getEventType());
+        if (AuditEventCategories.REVIEW_SIGN.equals(e.getEventCategory())) {
+            vo.setSignatureRole("REVIEW");
+        } else if (AuditEventCategories.PRIMARY_SIGN.equals(e.getEventCategory())) {
+            vo.setSignatureRole("PRIMARY");
+        }
+        vo.setActionCode(e.getActionCode());
+        vo.setActionLabel(e.getActionLabel());
+        vo.setSourceLayer(e.getSourceLayer());
+        vo.setTargetTable(e.getTargetTable());
+        vo.setTargetId(AuditQueryResponseSanitizer.hidesTargetId(e.getTargetTable())
+                ? null : e.getTargetId());
+        vo.setBizType(e.getBizType());
+        vo.setBizCode(e.getBizCode());
+        vo.setDiffCount(computeDiffCount(e.getFieldDiffs()));
+        vo.setReason(e.getReason());
+        vo.setRecordTitle(e.getRecordTitle());
+        vo.setEntryType(e.getEntryType());
+        vo.setEntryId(e.getEntryId());
+        vo.setEntryName(e.getEntryName());
+        vo.setCreatedAt(e.getCreatedAt());
+        // 瀛愪簨浠朵繚鐣欒嚜韬鍚嶆爣璁帮紱BUSINESS 涓昏浼氬湪缁勮鍚庢寜鍏宠仈绛惧悕閲嶆柊璧嬪�笺��
+        vo.setSigned(AuditConsts.TYPE_E_SIGNATURE.equals(e.getEventType()));
+        applySubTitles(vo, e.getExtra());
+        return vo;
+    }
+
+    /**
+     * 浠� extra.recordSubTitles 鍙栧瓙琛ㄦ爣棰樿锛屽彧涓嬪彂鍓� {@link #LIST_SUB_TITLE_LIMIT} 鏉� + 鎬绘暟銆�
+     * 涓嶆暣浣撲笅鍙� extra锛氭鏂囬噷闄� dataSnapshot 澶栬繕鏈夊悇绉嶆潵婧愭爣璁颁笌涓婁笅鏂囷紝鍒楄〃鎺ュ彛涓嶈甯﹀嚭鍘汇��
+     */
+    @SuppressWarnings("unchecked")
+    private void applySubTitles(AuditEventListVO vo, String extraJson) {
+        if (!StringUtils.hasText(extraJson)) {
+            return;
+        }
+        try {
+            Map<String, Object> extra = JsonUtil.stringToMap(extraJson);
+            Object raw = extra == null ? null : extra.get("recordSubTitles");
+            if (!(raw instanceof List)) {
+                return;
+            }
+            List<?> all = (List<?>) raw;
+            List<String> head = new ArrayList<>();
+            for (Object item : all) {
+                if (head.size() >= LIST_SUB_TITLE_LIMIT) {
+                    break;
+                }
+                if (item != null) {
+                    head.add(String.valueOf(item));
+                }
+            }
+            vo.setSubTitles(head);
+            vo.setSubTitleCount(all.size());
+        } catch (Throwable ignored) {
+            // extra 瑙f瀽澶辫触涓嶅奖鍝嶅垪琛ㄤ富琛屸�斺�旀爣棰樻槸閿︿笂娣昏姳锛屼笉鑳界偢鎺夋暣椤�
+        }
+    }
+
+    private AuditEventDetailVO toDetailVo(AuditEventEntity e) {
+        AuditEventDetailVO vo = new AuditEventDetailVO();
+        vo.setId(e.getId());
+        vo.setClientEventId(e.getClientEventId());
+        vo.setOperationId(e.getOperationId());
+        vo.setEventTime(e.getEventTime());
+        vo.setTenantId(e.getTenantId());
+        vo.setOperatorId(e.getOperatorId());
+        vo.setOperatorName(e.getOperatorName());
+        vo.setOperatorOrgId(e.getOperatorOrgId());
+        vo.setAppName(e.getAppName());
+        vo.setBizModule(e.getBizModule());
+        vo.setIp(e.getIp());
+        vo.setIpRegion(e.getIpRegion());
+        vo.setBrowser(e.getBrowser());
+        vo.setOs(e.getOs());
+        vo.setRequestUri(e.getRequestUri());
+        vo.setEventType(e.getEventType());
+        vo.setActionCode(e.getActionCode());
+        vo.setActionLabel(e.getActionLabel());
+        vo.setSourceLayer(e.getSourceLayer());
+        vo.setTargetTable(e.getTargetTable());
+        vo.setTargetId(AuditQueryResponseSanitizer.hidesTargetId(e.getTargetTable())
+                ? null : e.getTargetId());
+        vo.setBizType(e.getBizType());
+        vo.setBizCode(e.getBizCode());
+        String modelId = AuditQueryResponseSanitizer.sourceModelId(e.getExtra());
+        Map<String, jnpf.audit.diff.AuditFieldDiff.FieldMeta> metadata =
+                auditFormRegistry.resolveFieldMetadata(modelId, e.getTargetTable());
+        String sanitizedFieldDiffs = AuditQueryResponseSanitizer.sanitizeFieldDiffs(
+                e.getFieldDiffs(), metadata);
+        vo.setFieldDiffs(sanitizedFieldDiffs);
+        vo.setFieldDiffList(auditFieldDiffViewService.build(e, sanitizedFieldDiffs, metadata));
+        vo.setExtra(AuditQueryResponseSanitizer.sanitizeJson(e.getExtra()));
+        vo.setDataSnapshot(AuditQueryResponseSanitizer.sanitizeJson(e.getDataSnapshot(), metadata));
+        vo.setReason(e.getReason());
+        vo.setRecordTitle(e.getRecordTitle());
+        vo.setEntryType(e.getEntryType());
+        vo.setEntryId(e.getEntryId());
+        vo.setEntryName(e.getEntryName());
+        vo.setPrevHash(e.getPrevHash());
+        vo.setCreatedAt(e.getCreatedAt());
+        return vo;
+    }
+
+    /** field_diffs 鏄� VisualLogModel[] JSON锛涚┖/闈炴硶 JSON 涓�寰嬮檷绾т负 0锛屼笉璁╂憳瑕佽绠楃偢鍒楄〃鎺ュ彛銆� */
+    private Integer computeDiffCount(String fieldDiffs) {
+        if (!StringUtils.hasText(fieldDiffs)) {
+            return 0;
+        }
+        return AuditQueryResponseSanitizer.effectiveDiffCount(fieldDiffs);
+    }
+}

--
Gitblit v1.8.0