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<AuditClient> auditClientProvider;
|
|
/**
|
* 本方法的全部失败都被隔离:审计查询、上下文采集或投递异常不得改变 Preview 的业务结果。
|
*/
|
public void recordPreviewSuccess(String interfaceId,
|
DataInterfaceParamModel request,
|
Map<String, Object> rawRequest,
|
Map<String, String> 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<String, Object> 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<String> 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<String, String> parameters, Map<String, Object> rawRequest) {
|
if (parameters != null) {
|
for (Map.Entry<String, String> 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<String, Object> 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<String> parameterNames(Map<String, String> parameters) {
|
List<String> 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;
|
}
|
}
|