package jnpf.bizcommon.audit.service;
|
|
import jnpf.audit.AuditConsts;
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.bizcommon.audit.service.sign.AuditSignEvidence;
|
import jnpf.bizcommon.audit.service.sign.AuditSignEvidenceReader;
|
import jnpf.bizcommon.audit.service.sign.AuditSignMetaData;
|
import jnpf.util.JsonUtil;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
|
import java.nio.charset.StandardCharsets;
|
import java.security.MessageDigest;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.Map;
|
|
/** 为数据接口操作事实补充请求所携带的电子签名事实。 */
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class DataInterfaceSignAuditService {
|
|
private static final String SIGN_ACTION = "SIGN";
|
private static final String REQUEST_SIGN_FIELD = "__request_biz_sign__";
|
|
private final AuditSignEvidenceReader signEvidenceReader;
|
private final AuditEventService auditEventService;
|
|
/**
|
* 只处理显式携带 relatedSignId 的数据接口事件。异常向上交给审计投递器进入 spool 重试,
|
* 调用发生在审计服务内部,不会影响原始业务请求。
|
*/
|
public void recordRelatedSign(AuditEventDTO businessEvent) {
|
if (businessEvent == null || isBlank(businessEvent.getRelatedSignId())
|
|| !AuditConsts.TYPE_DATA_CHANGE.equals(businessEvent.getEventType())
|
|| !"DATA_INTERFACE".equals(businessEvent.getBizType())) {
|
return;
|
}
|
|
String signId = businessEvent.getRelatedSignId().trim();
|
Map<String, AuditSignEvidence> evidences = signEvidenceReader.read(
|
Collections.singletonList(signId), businessEvent.getTenantId());
|
AuditSignEvidence evidence = evidences.get(signId);
|
if (evidence == null) {
|
log.warn("[DATA-INTERFACE-AUDIT] 未查询到同租户签名记录,跳过签名事件 clientEventId={}",
|
businessEvent.getClientEventId());
|
return;
|
}
|
if (isBlank(evidence.operatorId())
|
|| !evidence.operatorId().equals(businessEvent.getOperatorId())) {
|
log.warn("[DATA-INTERFACE-AUDIT] 签名归属与当前操作人不一致,跳过签名事件 clientEventId={}",
|
businessEvent.getClientEventId());
|
return;
|
}
|
|
AuditSignMetaData metaData = AuditSignMetaData.parse(evidence.metaData());
|
String signatureRole = metaData.isReviewButton() ? "REVIEW" : "PRIMARY";
|
Map<String, Object> extra = new LinkedHashMap<>();
|
extra.put("signField", REQUEST_SIGN_FIELD);
|
extra.put("signatureRole", signatureRole);
|
extra.put("auditSource", "DATA_INTERFACE");
|
if (!isBlank(evidence.accountName())) {
|
extra.put("accountName", evidence.accountName());
|
}
|
if (!isBlank(evidence.opType())) {
|
extra.put("opType", evidence.opType());
|
}
|
|
AuditEventDTO signEvent = AuditEventDTO.builder()
|
.clientEventId(signClientEventId(businessEvent.getOperationId(), signId))
|
.operationId(businessEvent.getOperationId())
|
.eventTime(evidence.signedAt() == null
|
? businessEvent.getEventTime() : evidence.signedAt())
|
.tenantId(businessEvent.getTenantId())
|
.operatorId(evidence.operatorId())
|
.operatorName(businessEvent.getOperatorName())
|
.operatorOrgId(businessEvent.getOperatorOrgId())
|
.appName(businessEvent.getAppName())
|
.bizModule(businessEvent.getBizModule())
|
.ip(businessEvent.getIp())
|
.ipRegion(businessEvent.getIpRegion())
|
.browser(businessEvent.getBrowser())
|
.os(businessEvent.getOs())
|
.requestUri(businessEvent.getRequestUri())
|
.eventType(AuditConsts.TYPE_E_SIGNATURE)
|
.actionCode(SIGN_ACTION)
|
.actionLabel(metaData.isReviewButton() ? "复审签名" : "电子签名")
|
.sourceLayer(3)
|
.targetTable(AuditSignEvidenceReader.SIGN_TABLE)
|
.targetId(signId)
|
.bizType(businessEvent.getBizType())
|
.bizCode(businessEvent.getBizCode())
|
.reason(evidence.note())
|
.extra(JsonUtil.getObjectToString(extra))
|
.recordTitle(businessEvent.getRecordTitle())
|
.entryType(businessEvent.getEntryType())
|
.entryId(businessEvent.getEntryId())
|
.entryName(businessEvent.getEntryName())
|
.build();
|
auditEventService.saveIdempotent(signEvent);
|
}
|
|
private static String signClientEventId(String operationId, String signId) {
|
String source = value(operationId) + "\n" + value(signId);
|
try {
|
byte[] digest = MessageDigest.getInstance("SHA-256")
|
.digest(source.getBytes(StandardCharsets.UTF_8));
|
StringBuilder hex = new StringBuilder("DIS-");
|
for (byte item : digest) {
|
hex.append(String.format("%02x", item & 0xff));
|
}
|
return hex.toString();
|
} catch (Exception e) {
|
throw new IllegalStateException("生成数据接口签名事件幂等键失败", e);
|
}
|
}
|
|
private static String value(String text) {
|
return text == null ? "" : text;
|
}
|
|
private static boolean isBlank(String text) {
|
return text == null || text.trim().isEmpty();
|
}
|
}
|