fix(risk/report): close Phase 17 KEEP-VERIFY/EXTEND — 6 defects + 1 hidden compile defect (jakarta) + test-coverage blind zone (3 missing tables); add sensitive-access audit + ReportAndRiskPhase17IntegrationTest (7); full regression 488 green
This commit is contained in:
@@ -6,6 +6,11 @@ import com.danyang.match.platform.audit.entity.AuditLog;
|
||||
import com.danyang.match.platform.audit.mapper.AuditLogMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -61,4 +66,24 @@ public class AuditLogService {
|
||||
public Map<String, Object> getSensitiveAccessLogs(String operatorId, Integer page, Integer pageSize) {
|
||||
return listAuditLogs(null, null, operatorId, page, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* D3(Phase 17):记录后台敏感访问(查看他人风险画像 / 举报详情 / 风险事件列表)。
|
||||
* AGENTS.md #8 要求敏感数据默认拒绝,查看动作本身必须可溯源。
|
||||
*/
|
||||
@Transactional
|
||||
public void recordSensitiveAccess(String operatorId, String resourceType, String resourceId) {
|
||||
AuditLog auditLog = new AuditLog();
|
||||
auditLog.setOperatorId(operatorId);
|
||||
auditLog.setAction("SENSITIVE_ACCESS");
|
||||
auditLog.setResourceType(resourceType);
|
||||
auditLog.setResourceId(resourceId);
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
HttpServletRequest req = attrs.getRequest();
|
||||
auditLog.setIp(req.getRemoteAddr());
|
||||
String ua = req.getHeader("User-Agent");
|
||||
auditLog.setUserAgent(ua != null ? (ua.length() > 512 ? ua.substring(0, 512) : ua) : null);
|
||||
}
|
||||
auditLogMapper.insert(auditLog);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -3,6 +3,7 @@ package com.danyang.match.platform.risk.controller;
|
||||
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
|
||||
import com.danyang.match.platform.risk.dto.ReportProcessDTO;
|
||||
import com.danyang.match.platform.risk.service.ReportService;
|
||||
import com.danyang.match.platform.audit.service.AuditLogService;
|
||||
import com.danyang.match.platform.shared.dto.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -21,6 +22,7 @@ import java.util.Map;
|
||||
public class ReportOperatorController {
|
||||
|
||||
private final ReportService reportService;
|
||||
private final AuditLogService auditLogService;
|
||||
|
||||
/**
|
||||
* 获取举报列表(可带 status 筛选)
|
||||
@@ -63,6 +65,7 @@ public class ReportOperatorController {
|
||||
public ApiResponse<Map<String, Object>> getReportDetail(
|
||||
@AuthenticationPrincipal OperatorPrincipal principal,
|
||||
@PathVariable String id) {
|
||||
auditLogService.recordSensitiveAccess(principal.id(), "report", id);
|
||||
return ApiResponse.ok(reportService.getReportDetail(id));
|
||||
}
|
||||
|
||||
@@ -99,6 +102,7 @@ public class ReportOperatorController {
|
||||
public ApiResponse<Map<String, Object>> getUserRiskProfile(
|
||||
@AuthenticationPrincipal OperatorPrincipal principal,
|
||||
@PathVariable String userId) {
|
||||
auditLogService.recordSensitiveAccess(principal.id(), "user_risk_profile", userId);
|
||||
return ApiResponse.ok(reportService.getUserRiskProfile(userId));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.danyang.match.platform.risk.controller;
|
||||
|
||||
import com.danyang.match.platform.risk.entity.RiskEvent;
|
||||
import com.danyang.match.platform.risk.service.RiskReferralService;
|
||||
import com.danyang.match.platform.audit.service.AuditLogService;
|
||||
import com.danyang.match.platform.shared.dto.ApiResponse;
|
||||
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
||||
public class RiskController {
|
||||
|
||||
private final RiskReferralService riskReferralService;
|
||||
private final AuditLogService auditLogService;
|
||||
|
||||
/**
|
||||
* P17-T04: 运营人员手动标记风险
|
||||
@@ -48,6 +50,7 @@ public class RiskController {
|
||||
public ApiResponse<List<RiskEvent>> getRiskEvents(
|
||||
@AuthenticationPrincipal OperatorPrincipal principal,
|
||||
@PathVariable String userId) {
|
||||
auditLogService.recordSensitiveAccess(principal.id(), "risk_event", userId);
|
||||
return ApiResponse.ok(riskReferralService.getRiskEventsByUser(userId));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface RiskEventMapper extends BaseMapper<RiskEvent> {
|
||||
|
||||
@Select("SELECT * FROM risk_events WHERE user_id = #{userId} ORDER BY created_at DESC LIMIT 20")
|
||||
// D5(Phase 17):与 sumScoreByUserId / findByEventType 保持一致,过滤软删事件,
|
||||
// 否则 getUserRiskProfile / getReportDetail 的 recentEvents 会混入已删除事件,
|
||||
// 与已过滤的风险评分不一致(静默功能失效)。
|
||||
@Select("SELECT * FROM risk_events WHERE user_id = #{userId} AND deleted_at IS NULL ORDER BY created_at DESC LIMIT 20")
|
||||
List<RiskEvent> findByUserId(String userId);
|
||||
|
||||
@Select("SELECT COALESCE(SUM(score), 0) FROM risk_events WHERE user_id = #{userId} AND deleted_at IS NULL")
|
||||
|
||||
@@ -21,6 +21,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
@@ -383,6 +387,20 @@ public class ReportService {
|
||||
auditLog.setAction(action);
|
||||
auditLog.setResourceType(resourceType);
|
||||
auditLog.setResourceId(resourceId);
|
||||
auditLog.setBeforeData(beforeData);
|
||||
auditLog.setAfterData(afterData);
|
||||
// D4(Phase 17):补全审计来源(IP / User-Agent),敏感访问与处置审计必须可溯源。
|
||||
enrichAuditSource(auditLog);
|
||||
auditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
// D4(Phase 17):从当前请求上下文补全审计的 IP 与 User-Agent(与 AuthorizationAuditListener 同模式)。
|
||||
private void enrichAuditSource(AuditLog auditLog) {
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
HttpServletRequest req = attrs.getRequest();
|
||||
auditLog.setIp(req.getRemoteAddr());
|
||||
String ua = req.getHeader("User-Agent");
|
||||
auditLog.setUserAgent(ua != null ? (ua.length() > 512 ? ua.substring(0, 512) : ua) : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -5,10 +5,16 @@ import com.danyang.match.platform.risk.entity.RiskEvent;
|
||||
import com.danyang.match.platform.risk.mapper.RiskActionMapper;
|
||||
import com.danyang.match.platform.risk.mapper.RiskEventMapper;
|
||||
import com.danyang.match.platform.shared.constant.Constants;
|
||||
import com.danyang.match.platform.audit.entity.AuditLog;
|
||||
import com.danyang.match.platform.audit.mapper.AuditLogMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@@ -25,6 +31,7 @@ public class RiskReferralService {
|
||||
|
||||
private final RiskEventMapper riskEventMapper;
|
||||
private final RiskActionMapper riskActionMapper;
|
||||
private final AuditLogMapper auditLogMapper;
|
||||
|
||||
/**
|
||||
* P17-T01: 咨询内容风险检测
|
||||
@@ -113,7 +120,10 @@ public class RiskReferralService {
|
||||
*/
|
||||
@Transactional
|
||||
public RiskEvent manualFlagRisk(String userId, String eventType, String note, String createdBy) {
|
||||
return createRiskEvent(userId, eventType, 5, "manual", createdBy, note);
|
||||
RiskEvent event = createRiskEvent(userId, eventType, 5, "manual", createdBy, note);
|
||||
// D2(Phase 17):人工风险标记属处置类操作,必须审计("处置继续审计")。
|
||||
saveAuditLog(createdBy, "MANUAL_FLAG_RISK", "risk_event", event.getId(), null, null);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,4 +151,23 @@ public class RiskReferralService {
|
||||
log.info("创建风险事件: userId={}, eventType={}, score={}, source={}", userId, eventType, score, source);
|
||||
return event;
|
||||
}
|
||||
|
||||
// D2(Phase 17):与 ReportService 同模式的审计写入(含 IP / User-Agent 溯源)。
|
||||
private void saveAuditLog(String operatorId, String action, String resourceType,
|
||||
String resourceId, String beforeData, String afterData) {
|
||||
AuditLog auditLog = new AuditLog();
|
||||
auditLog.setOperatorId(operatorId);
|
||||
auditLog.setAction(action);
|
||||
auditLog.setResourceType(resourceType);
|
||||
auditLog.setResourceId(resourceId);
|
||||
auditLog.setBeforeData(beforeData);
|
||||
auditLog.setAfterData(afterData);
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
HttpServletRequest req = attrs.getRequest();
|
||||
auditLog.setIp(req.getRemoteAddr());
|
||||
String ua = req.getHeader("User-Agent");
|
||||
auditLog.setUserAgent(ua != null ? (ua.length() > 512 ? ua.substring(0, 512) : ua) : null);
|
||||
}
|
||||
auditLogMapper.insert(auditLog);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user