package jnpf.base.service; import jnpf.audit.AuditConsts; import jnpf.audit.AuditOperationIds; import jnpf.audit.model.AuditEventDTO; import jnpf.audit.sdk.AuditClient; import jnpf.base.entity.DataInterfaceEntity; import jnpf.base.entity.ModuleEntity; import jnpf.base.model.datainterface.DataInterfaceParamModel; import jnpf.util.JsonUtil; import jnpf.util.ServletUtil; import jnpf.util.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; /** 记录 Preview 入口成功执行的 SQL 写操作事实。 */ @Slf4j @Component @RequiredArgsConstructor public class DataInterfaceAuditRecorder { private static final String BIZ_SIGN = "biz_sign"; private static final String BIZ_TYPE = "DATA_INTERFACE"; private static final String ACTION_EXECUTE = "DATA_INTERFACE_EXECUTE"; private final DataInterfaceService dataInterfaceService; private final ModuleService moduleService; private final ObjectProvider auditClientProvider; /** * 本方法的全部失败都被隔离:审计查询、上下文采集或投递异常不得改变 Preview 的业务结果。 */ public void recordPreviewSuccess(String interfaceId, DataInterfaceParamModel request, Map rawRequest, Map parameters) { try { String bizSign = findBizSign(parameters, rawRequest); if (StringUtil.isEmpty(bizSign)) { return; } DataInterfaceEntity entity = dataInterfaceService.getInfo(interfaceId); if (!isSqlWrite(entity)) { return; } AuditClient auditClient = auditClientProvider.getIfAvailable(); if (auditClient == null) { log.warn("[DATA-INTERFACE-AUDIT] AuditClient 未装配,跳过 interfaceId={}", interfaceId); return; } String menuId = currentMenuId(); String menuName = resolveMenuName(menuId); Map extra = new LinkedHashMap<>(); extra.put("auditSource", "DATA_INTERFACE"); extra.put("endpoint", "PREVIEW"); extra.put("dataInterfaceId", entity.getId()); extra.put("dataInterfaceCode", entity.getEnCode()); extra.put("dataInterfaceName", entity.getFullName()); extra.put("interfaceAction", entity.getAction()); if (request != null && StringUtil.isNotEmpty(request.getOrigin())) { extra.put("origin", request.getOrigin()); } List parameterNames = parameterNames(parameters); if (!parameterNames.isEmpty()) { extra.put("parameterNames", parameterNames); } if (StringUtil.isEmpty(menuId)) { extra.put("entrySource", "degraded"); } else if (StringUtil.isEmpty(menuName)) { extra.put("entrySource", "unresolved"); } AuditEventDTO event = AuditEventDTO.builder() .operationId(AuditOperationIds.correlation(bizSign)) .tenantId(request == null || StringUtil.isEmpty(request.getTenantId()) ? null : request.getTenantId()) .bizModule(menuName) .requestUri(currentRequestUri()) .eventType(AuditConsts.TYPE_DATA_CHANGE) .actionCode(ACTION_EXECUTE) .actionLabel(StringUtil.isEmpty(entity.getFullName()) ? "执行SQL数据接口" : entity.getFullName()) .sourceLayer(2) .bizType(BIZ_TYPE) .bizCode(entity.getEnCode()) .recordTitle(entity.getFullName()) .entryType(StringUtil.isEmpty(menuId) ? null : "MENU") .entryId(menuId) .entryName(menuName) .extra(JsonUtil.getObjectToString(extra)) .relatedSignId(bizSign) .build(); auditClient.record(event); } catch (Throwable t) { log.error("[DATA-INTERFACE-AUDIT] 记录审计失败,Preview 业务结果不受影响 interfaceId={}", interfaceId, t); } } private static boolean isSqlWrite(DataInterfaceEntity entity) { return entity != null && Integer.valueOf(1).equals(entity.getType()) && entity.getAction() != null && !Integer.valueOf(3).equals(entity.getAction()); } private static String findBizSign(Map parameters, Map rawRequest) { if (parameters != null) { for (Map.Entry entry : parameters.entrySet()) { if (entry.getKey() != null && BIZ_SIGN.equals(entry.getKey().trim().toLowerCase(Locale.ROOT))) { return trim(entry.getValue()); } } } if (rawRequest != null) { for (Map.Entry entry : rawRequest.entrySet()) { if (entry.getKey() != null && BIZ_SIGN.equals(entry.getKey().trim().toLowerCase(Locale.ROOT))) { return trim(entry.getValue()); } } } return null; } private static List parameterNames(Map parameters) { List names = new ArrayList<>(); if (parameters == null) { return names; } for (String name : parameters.keySet()) { if (name != null && !BIZ_SIGN.equals(name.trim().toLowerCase(Locale.ROOT))) { names.add(name); } } return names; } private static String currentMenuId() { try { return trim(ServletUtil.getRequest().getHeader("Jnpf-Menu-Id")); } catch (Throwable ignored) { return null; } } private String resolveMenuName(String menuId) { if (StringUtil.isEmpty(menuId)) { return null; } try { ModuleEntity menu = moduleService.getInfo(menuId); if (menu == null || Integer.valueOf(1).equals(menu.getDeleteMark())) { return null; } return trim(menu.getFullName()); } catch (Throwable t) { log.warn("[DATA-INTERFACE-AUDIT] 查询菜单名称失败,审计事件降级记录 menuId={}", menuId, t); return null; } } private static String currentRequestUri() { try { return ServletUtil.getServletPath(); } catch (Throwable ignored) { return null; } } private static String trim(Object value) { if (value == null) { return null; } String text = String.valueOf(value).trim(); return text.isEmpty() ? null : text; } }