修复V4审计查询误匹配和脱敏
This commit is contained in:
@@ -43,6 +43,8 @@ import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationTaskCardMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationTaskMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -70,6 +72,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
private final ReservationOperaOperationMapper operaOperationMapper;
|
||||
private final ReservationOperaOperationAttemptMapper operaOperationAttemptMapper;
|
||||
private final ReservationAuditLogMapper auditLogMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 注入本工作流 Mapper,Repository 负责实体转换和查询细节。
|
||||
@@ -82,7 +85,8 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
ReservationTaskCardMapper taskCardMapper,
|
||||
ReservationOperaOperationMapper operaOperationMapper,
|
||||
ReservationOperaOperationAttemptMapper operaOperationAttemptMapper,
|
||||
ReservationAuditLogMapper auditLogMapper) {
|
||||
ReservationAuditLogMapper auditLogMapper,
|
||||
ObjectMapper objectMapper) {
|
||||
this.batchMapper = batchMapper;
|
||||
this.transitionMapper = transitionMapper;
|
||||
this.orderMapper = orderMapper;
|
||||
@@ -91,6 +95,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
this.operaOperationMapper = operaOperationMapper;
|
||||
this.operaOperationAttemptMapper = operaOperationAttemptMapper;
|
||||
this.auditLogMapper = auditLogMapper;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -941,24 +946,53 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
return List.of();
|
||||
}
|
||||
String targetFieldToken = "\"" + targetFieldName + "\"";
|
||||
String targetIdToken = "\"" + targetId + "\"";
|
||||
return auditLogMapper.selectList(Wrappers.<ReservationAuditLogEntity>lambdaQuery()
|
||||
.eq(ReservationAuditLogEntity::getHotelId, hotelId)
|
||||
.in(ReservationAuditLogEntity::getAction, actions)
|
||||
.and(wrapper -> wrapper
|
||||
.and(beforeSnapshot -> beforeSnapshot
|
||||
.like(ReservationAuditLogEntity::getBeforeSnapshotJson, targetFieldToken)
|
||||
.like(ReservationAuditLogEntity::getBeforeSnapshotJson, targetIdToken))
|
||||
.or(afterSnapshot -> afterSnapshot
|
||||
.like(ReservationAuditLogEntity::getAfterSnapshotJson, targetFieldToken)
|
||||
.like(ReservationAuditLogEntity::getAfterSnapshotJson, targetIdToken)))
|
||||
.like(ReservationAuditLogEntity::getBeforeSnapshotJson, targetFieldToken)
|
||||
.or()
|
||||
.like(ReservationAuditLogEntity::getAfterSnapshotJson, targetFieldToken))
|
||||
.orderByAsc(ReservationAuditLogEntity::getOccurredAt)
|
||||
.orderByAsc(ReservationAuditLogEntity::getId))
|
||||
.stream()
|
||||
.map(this::toAuditLogSnapshot)
|
||||
.filter(snapshot -> auditSnapshotContainsTarget(snapshot, targetFieldName, targetId.toString()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断审计前后快照中是否精确指向目标对象,避免同一 JSON 中其他字段包含目标 ID 时误命中。
|
||||
*/
|
||||
private boolean auditSnapshotContainsTarget(
|
||||
ReservationAuditLogSnapshot snapshot,
|
||||
String targetFieldName,
|
||||
String targetId) {
|
||||
return auditPayloadContainsTarget(snapshot.beforeSnapshotJson(), targetFieldName, targetId)
|
||||
|| auditPayloadContainsTarget(snapshot.afterSnapshotJson(), targetFieldName, targetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析审计 JSON 顶层字段并比较目标 ID。解析失败的历史异常快照不参与 V4 审计关联。
|
||||
*/
|
||||
private boolean auditPayloadContainsTarget(String payloadJson, String targetFieldName, String targetId) {
|
||||
if (!hasText(payloadJson)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JsonNode payload = objectMapper.readTree(payloadJson);
|
||||
JsonNode value = payload == null ? null : payload.get(targetFieldName);
|
||||
if (value == null || value.isNull() || value.isMissingNode()) {
|
||||
return false;
|
||||
}
|
||||
return value.isTextual() || value.isNumber()
|
||||
? targetId.equals(value.asText())
|
||||
: false;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按业务 key 查询 AI transition。该查询只用于补充待处理任务上下文,不替代订单查询。
|
||||
*/
|
||||
|
||||
@@ -502,6 +502,9 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
if (payload.isTextual() && isSensitiveAuditValue(payload.asText())) {
|
||||
return NullNode.getInstance();
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -513,13 +516,44 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
return "raw".equals(normalized)
|
||||
|| normalized.endsWith("_raw")
|
||||
|| normalized.contains("payload_json")
|
||||
|| normalized.contains("ai_payload")
|
||||
|| normalized.contains("raw_payload")
|
||||
|| normalized.contains("private_url")
|
||||
|| normalized.contains("attachment_url")
|
||||
|| normalized.contains("download_url")
|
||||
|| normalized.contains("signed_url")
|
||||
|| "url".equals(normalized)
|
||||
|| normalized.endsWith("_url")
|
||||
|| normalized.contains("secret")
|
||||
|| normalized.contains("token")
|
||||
|| normalized.contains("api_key")
|
||||
|| normalized.contains("apikey")
|
||||
|| normalized.contains("cookie")
|
||||
|| normalized.contains("authorization")
|
||||
|| normalized.contains("password")
|
||||
|| normalized.contains("plain_text")
|
||||
|| normalized.contains("email_text")
|
||||
|| normalized.contains("text_body")
|
||||
|| normalized.contains("body")
|
||||
|| normalized.contains("html");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断审计字段值是否像敏感链接或认证头,避免未命中字段名规则时泄漏 OSS / 签名 URL。
|
||||
*/
|
||||
private boolean isSensitiveAuditValue(String value) {
|
||||
if (!hasText(value)) {
|
||||
return false;
|
||||
}
|
||||
String normalized = value.trim().toLowerCase(Locale.ROOT);
|
||||
return normalized.startsWith("http://")
|
||||
|| normalized.startsWith("https://")
|
||||
|| normalized.startsWith("oss://")
|
||||
|| normalized.startsWith("s3://")
|
||||
|| normalized.startsWith("bearer ")
|
||||
|| normalized.startsWith("basic ");
|
||||
}
|
||||
|
||||
private ReservationV4CardCountsResult cardCounts(List<ReservationV4TaskCardSnapshot> cards) {
|
||||
List<ReservationV4TaskCardSnapshot> safeCards = cards == null ? List.of() : cards;
|
||||
return new ReservationV4CardCountsResult(
|
||||
|
||||
Reference in New Issue
Block a user