实战案例:保险理赔线上审核系统的技术实现
场景需求分析
业务背景
保险理赔审核是保险公司的核心业务流程,涉及报案受理、材料审核、定损评估、赔付计算等多个环节。传统的人工审核方式效率低下,且容易出现人为错误。通过责任链模式实现的线上审核系统,可以实现自动化审核与人工审核的有机结合,提高审核效率和准确性。
核心业务流程
flowchart TD
A[客户报案] --> B[报案受理]
B --> C[材料完整性检查]
C --> D[基础信息验证]
D --> E[风险评估]
E --> F{是否需要人工审核}
F -->|是| G[人工审核队列]
F -->|否| H[自动定损评估]
G --> I[人工审核]
I --> J[审核结果确认]
H --> K[赔付金额计算]
K --> L[最终审核]
L --> M[赔付执行]
J --> N{审核通过}
N -->|是| K
N -->|否| O[拒赔处理]
M --> P[结案]
O --> P
业务需求
- 多级审核权限:根据赔付金额和案件复杂度设置不同的审核权限
- 自动化审核:对于简单案件实现全自动审核
- 人工审核:复杂案件转入人工审核流程
- 审核时效:确保审核在规定时间内完成
- 审核记录:完整记录审核过程和决策依据
- 风险控制:识别和防范欺诈风险
技术挑战
- 复杂的业务规则:不同险种、不同情况下的审核规则差异很大
- 权限控制:需要精确控制不同级别审核员的权限
- 状态管理:案件在不同审核环节间的状态流转
- 并发处理:支持多个审核员同时处理不同案件
- 审核时效:超时案件的自动升级和提醒机制
责任链模式设计思路
整体架构设计
classDiagram
class ClaimContext {
-claimId: String
-claimType: ClaimType
-claimAmount: BigDecimal
-claimStatus: ClaimStatus
-documents: List~Document~
-auditHistory: List~AuditRecord~
-riskLevel: RiskLevel
+addAuditRecord(AuditRecord record)
+getCurrentAuditor() String
+isTimeoutExceeded() boolean
}
class ClaimAuditHandler {
<>
+handle(ClaimContext context) boolean
+getRequiredRole() Role
+getMaxAmount() BigDecimal
+isAutomated() boolean
+getTimeoutMinutes() int
}
class ClaimAcceptanceHandler {
+handle(ClaimContext context) boolean
-validateClaimInfo(ClaimInfo info) boolean
-checkPolicyStatus(String policyId) boolean
}
class DocumentVerificationHandler {
+handle(ClaimContext context) boolean
-verifyDocumentCompleteness(List~Document~ docs) boolean
-validateDocumentAuthenticity(Document doc) boolean
}
class RiskAssessmentHandler {
+handle(ClaimContext context) boolean
-calculateRiskScore(ClaimContext context) double
-checkFraudIndicators(ClaimContext context) List~String~
}
class AutomatedAuditHandler {
+handle(ClaimContext context) boolean
-applyBusinessRules(ClaimContext context) AuditResult
-calculateClaimAmount(ClaimContext context) BigDecimal
}
class ManualAuditHandler {
+handle(ClaimContext context) boolean
-assignToAuditor(ClaimContext context) String
-waitForAuditResult(ClaimContext context) AuditResult
}
ClaimAuditHandler <|-- ClaimAcceptanceHandler
ClaimAuditHandler <|-- DocumentVerificationHandler
ClaimAuditHandler <|-- RiskAssessmentHandler
ClaimAuditHandler <|-- AutomatedAuditHandler
ClaimAuditHandler <|-- ManualAuditHandler
设计原则
- 权限分级:不同处理器对应不同的审核权限级别
- 自动化优先:优先使用自动化审核,减少人工干预
- 风险控制:内置风险评估和欺诈检测机制
- 可追溯性:完整记录审核过程和决策依据
- 时效管理:支持超时处理和自动升级
核心代码实现
1. 理赔审核上下文定义
java
/**
* 理赔审核上下文
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ClaimContext {
// 基本信息
private String claimId;
private String policyId;
private String customerId;
private ClaimType claimType;
private BigDecimal claimAmount;
private ClaimStatus claimStatus;
private LocalDateTime claimTime;
// 审核信息
private String currentAuditor;
private RiskLevel riskLevel;
private List<AuditRecord> auditHistory = new ArrayList<>();
private Map<String, Object> auditData = new HashMap<>();
// 文档材料
private List<Document> documents = new ArrayList<>();
private Map<String, Boolean> documentVerificationResults = new HashMap<>();
// 业务数据
private PolicyInfo policyInfo;
private CustomerInfo customerInfo;
private ClaimDetails claimDetails;
// 审核结果
private AuditResult finalAuditResult;
private BigDecimal approvedAmount;
private String rejectReason;
// 时效管理
private LocalDateTime auditStartTime;
private LocalDateTime auditDeadline;
private int timeoutMinutes;
/**
* 添加审核记录
*/
public void addAuditRecord(AuditRecord record) {
auditHistory.add(record);
// 更新当前状态
this.claimStatus = record.getResultStatus();
this.currentAuditor = record.getAuditorId();
}
/**
* 设置审核数据
*/
public void setAuditData(String key, Object value) {
auditData.put(key, value);
}
/**
* 获取审核数据
*/
@SuppressWarnings("unchecked")
public <T> T getAuditData(String key, Class<T> type) {
Object value = auditData.get(key);
if (value != null && type.isInstance(value)) {
return (T) value;
}
return null;
}
/**
* 检查是否超时
*/
public boolean isTimeoutExceeded() {
if (auditDeadline == null) {
return false;
}
return LocalDateTime.now().isAfter(auditDeadline);
}
/**
* 获取剩余审核时间(分钟)
*/
public long getRemainingMinutes() {
if (auditDeadline == null) {
return -1;
}
return Duration.between(LocalDateTime.now(), auditDeadline).toMinutes();
}
/**
* 检查是否需要人工审核
*/
public boolean requiresManualAudit() {
// 高风险案件需要人工审核
if (riskLevel == RiskLevel.HIGH) {
return true;
}
// 大额案件需要人工审核
if (claimAmount.compareTo(new BigDecimal("50000")) > 0) {
return true;
}
// 复杂案件类型需要人工审核
if (claimType == ClaimType.COMPLEX_MEDICAL ||
claimType == ClaimType.MAJOR_ACCIDENT) {
return true;
}
return false;
}
/**
* 获取所需审核权限级别
*/
public AuditLevel getRequiredAuditLevel() {
if (claimAmount.compareTo(new BigDecimal("100000")) > 0) {
return AuditLevel.SENIOR;
} else if (claimAmount.compareTo(new BigDecimal("20000")) > 0) {
return AuditLevel.INTERMEDIATE;
} else {
return AuditLevel.JUNIOR;
}
}
}
/**
* 理赔类型枚举
*/
public enum ClaimType {
SIMPLE_MEDICAL("简单医疗"),
COMPLEX_MEDICAL("复杂医疗"),
VEHICLE_DAMAGE("车辆损失"),
PROPERTY_DAMAGE("财产损失"),
PERSONAL_INJURY("人身伤害"),
MAJOR_ACCIDENT("重大事故");
private final String description;
ClaimType(String description) {
this.description = description;
}
}
/**
* 理赔状态枚举
*/
public enum ClaimStatus {
SUBMITTED("已提交"),
UNDER_REVIEW("审核中"),
PENDING_DOCUMENTS("待补充材料"),
RISK_ASSESSMENT("风险评估中"),
MANUAL_AUDIT("人工审核中"),
APPROVED("审核通过"),
REJECTED("审核拒绝"),
PAID("已赔付"),
CLOSED("已结案");
private final String description;
ClaimStatus(String description) {
this.description = description;
}
}
/**
* 风险级别枚举
*/
public enum RiskLevel {
LOW("低风险"),
MEDIUM("中风险"),
HIGH("高风险"),
CRITICAL("极高风险");
private final String description;
RiskLevel(String description) {
this.description = description;
}
}
/**
* 审核级别枚举
*/
public enum AuditLevel {
JUNIOR("初级审核员"),
INTERMEDIATE("中级审核员"),
SENIOR("高级审核员"),
MANAGER("审核经理");
private final String description;
AuditLevel(String description) {
this.description = description;
}
}
/**
* 审核记录
*/
@Data
@Builder
public class AuditRecord {
private String recordId;
private String auditorId;
private String auditorName;
private AuditLevel auditorLevel;
private LocalDateTime auditTime;
private String auditStep;
private AuditResult auditResult;
private ClaimStatus resultStatus;
private String comments;
private Map<String, Object> auditData;
}
/**
* 审核结果枚举
*/
public enum AuditResult {
APPROVED("通过"),
REJECTED("拒绝"),
PENDING("待处理"),
ESCALATED("升级处理"),
RETURNED("退回补充");
private final String description;
AuditResult(String description) {
this.description = description;
}
}
2. 抽象理赔审核处理器
java
/**
* 抽象理赔审核处理器
*/
@Slf4j
public abstract class AbstractClaimAuditHandler
extends AbstractHandler<ClaimContext, AuditResult> {
@Override
protected final boolean doHandle(Context<ClaimContext, AuditResult> context) {
ClaimContext claimContext = context.getRequest();
String handlerName = this.getClass().getSimpleName();
try {
// 检查权限
if (!checkAuditPermission(claimContext)) {
log.warn("Insufficient permission for handler: {}, claim: {}",
handlerName, claimContext.getClaimId());
return false;
}
// 检查超时
if (claimContext.isTimeoutExceeded()) {
log.warn("Audit timeout exceeded for claim: {}", claimContext.getClaimId());
handleTimeout(claimContext);
return false;
}
// 记录审核开始
recordAuditStart(claimContext);
// 执行具体审核逻辑
AuditResult result = executeAudit(claimContext);
// 记录审核结果
recordAuditResult(claimContext, result);
// 设置响应结果
context.setResponse(result);
// 根据结果决定是否继续
return shouldContinue(result);
} catch (Exception e) {
log.error("Audit failed for handler: {}, claim: {}",
handlerName, claimContext.getClaimId(), e);
// 记录审核异常
recordAuditError(claimContext, e);
return false;
}
}
/**
* 执行具体的审核逻辑
*/
protected abstract AuditResult executeAudit(ClaimContext context) throws Exception;
/**
* 获取处理器名称
*/
protected abstract String getHandlerName();
/**
* 获取所需审核权限级别
*/
protected AuditLevel getRequiredAuditLevel() {
return AuditLevel.JUNIOR;
}
/**
* 获取最大可审核金额
*/
protected BigDecimal getMaxAuditAmount() {
return new BigDecimal("1000000"); // 默认100万
}
/**
* 是否为自动化审核
*/
protected boolean isAutomated() {
return true;
}
/**
* 获取审核超时时间(分钟)
*/
protected int getTimeoutMinutes() {
return 60; // 默认1小时
}
/**
* 检查审核权限
*/
protected boolean checkAuditPermission(ClaimContext context) {
// 检查金额权限
if (context.getClaimAmount().compareTo(getMaxAuditAmount()) > 0) {
return false;
}
// 检查审核级别权限
AuditLevel requiredLevel = context.getRequiredAuditLevel();
AuditLevel handlerLevel = getRequiredAuditLevel();
return handlerLevel.ordinal() >= requiredLevel.ordinal();
}
/**
* 处理超时情况
*/
protected void handleTimeout(ClaimContext context) {
// 记录超时
AuditRecord timeoutRecord = AuditRecord.builder()
.recordId(UUID.randomUUID().toString())
.auditorId("SYSTEM")
.auditorName("系统")
.auditTime(LocalDateTime.now())
.auditStep(getHandlerName())
.auditResult(AuditResult.ESCALATED)
.resultStatus(ClaimStatus.MANUAL_AUDIT)
.comments("审核超时,自动升级到人工审核")
.build();
context.addAuditRecord(timeoutRecord);
// 发送超时通知
sendTimeoutNotification(context);
}
/**
* 记录审核开始
*/
protected void recordAuditStart(ClaimContext context) {
if (context.getAuditStartTime() == null) {
context.setAuditStartTime(LocalDateTime.now());
// 设置审核截止时间
LocalDateTime deadline = LocalDateTime.now().plusMinutes(getTimeoutMinutes());
context.setAuditDeadline(deadline);
context.setTimeoutMinutes(getTimeoutMinutes());
}
log.info("Starting audit step: {} for claim: {}",
getHandlerName(), context.getClaimId());
}
/**
* 记录审核结果
*/
protected void recordAuditResult(ClaimContext context, AuditResult result) {
AuditRecord record = AuditRecord.builder()
.recordId(UUID.randomUUID().toString())
.auditorId(isAutomated() ? "SYSTEM" : context.getCurrentAuditor())
.auditorName(isAutomated() ? "自动审核" : getAuditorName(context.getCurrentAuditor()))
.auditorLevel(getRequiredAuditLevel())
.auditTime(LocalDateTime.now())
.auditStep(getHandlerName())
.auditResult(result)
.resultStatus(mapResultToStatus(result))
.comments(generateAuditComments(context, result))
.auditData(new HashMap<>(context.getAuditData()))
.build();
context.addAuditRecord(record);
log.info("Audit step completed: {} for claim: {}, result: {}",
getHandlerName(), context.getClaimId(), result);
}
/**
* 记录审核异常
*/
protected void recordAuditError(ClaimContext context, Exception e) {
AuditRecord errorRecord = AuditRecord.builder()
.recordId(UUID.randomUUID().toString())
.auditorId("SYSTEM")
.auditorName("系统")
.auditTime(LocalDateTime.now())
.auditStep(getHandlerName())
.auditResult(AuditResult.ESCALATED)
.resultStatus(ClaimStatus.MANUAL_AUDIT)
.comments("审核异常:" + e.getMessage())
.build();
context.addAuditRecord(errorRecord);
}
/**
* 判断是否应该继续处理
*/
protected boolean shouldContinue(AuditResult result) {
return result == AuditResult.APPROVED || result == AuditResult.PENDING;
}
/**
* 将审核结果映射为状态
*/
protected ClaimStatus mapResultToStatus(AuditResult result) {
switch (result) {
case APPROVED:
return ClaimStatus.APPROVED;
case REJECTED:
return ClaimStatus.REJECTED;
case ESCALATED:
return ClaimStatus.MANUAL_AUDIT;
case RETURNED:
return ClaimStatus.PENDING_DOCUMENTS;
default:
return ClaimStatus.UNDER_REVIEW;
}
}
/**
* 生成审核评论
*/
protected String generateAuditComments(ClaimContext context, AuditResult result) {
return String.format("%s审核%s", getHandlerName(), result.getDescription());
}
/**
* 获取审核员姓名
*/
protected String getAuditorName(String auditorId) {
// 从用户服务获取审核员姓名
return "审核员";
}
/**
* 发送超时通知
*/
protected void sendTimeoutNotification(ClaimContext context) {
// 发送超时通知给相关人员
log.warn("Audit timeout notification sent for claim: {}", context.getClaimId());
}
}
3. 具体审核处理器实现
报案受理处理器
java
/**
* 报案受理处理器
*/
@Component
@Slf4j
public class ClaimAcceptanceHandler extends AbstractClaimAuditHandler {
@Autowired
private PolicyService policyService;
@Autowired
private CustomerService customerService;
@Override
protected AuditResult executeAudit(ClaimContext context) throws Exception {
// 1. 验证保单信息
if (!validatePolicy(context)) {
return AuditResult.REJECTED;
}
// 2. 验证客户信息
if (!validateCustomer(context)) {
return AuditResult.REJECTED;
}
// 3. 验证理赔信息
if (!validateClaimInfo(context)) {
return AuditResult.REJECTED;
}
// 4. 检查理赔时效
if (!checkClaimTimeliness(context)) {
return AuditResult.REJECTED;
}
// 受理成功
context.setClaimStatus(ClaimStatus.UNDER_REVIEW);
return AuditResult.APPROVED;
}
@Override
protected String getHandlerName() {
return "报案受理";
}
@Override
protected int getTimeoutMinutes() {
return 30; // 30分钟内完成受理
}
/**
* 验证保单信息
*/
private boolean validatePolicy(ClaimContext context) {
try {
PolicyInfo policy = policyService.getPolicyInfo(context.getPolicyId());
if (policy == null) {
context.setRejectReason("保单不存在");
return false;
}
if (!policy.isActive()) {
context.setRejectReason("保单已失效");
return false;
}
if (policy.isExpired()) {
context.setRejectReason("保单已过期");
return false;
}
// 检查保险责任
if (!policy.coversClaimType(context.getClaimType())) {
context.setRejectReason("不在保险责任范围内");
return false;
}
context.setPolicyInfo(policy);
return true;
} catch (Exception e) {
log.error("Failed to validate policy: {}", context.getPolicyId(), e);
context.setRejectReason("保单验证失败");
return false;
}
}
/**
* 验证客户信息
*/
private boolean validateCustomer(ClaimContext context) {
try {
CustomerInfo customer = customerService.getCustomerInfo(context.getCustomerId());
if (customer == null) {
context.setRejectReason("客户信息不存在");
return false;
}
if (!customer.isActive()) {
context.setRejectReason("客户账户已冻结");
return false;
}
// 验证客户与保单的关系
if (!customer.getId().equals(context.getPolicyInfo().getPolicyHolderId())) {
context.setRejectReason("客户与保单持有人不匹配");
return false;
}
context.setCustomerInfo(customer);
return true;
} catch (Exception e) {
log.error("Failed to validate customer: {}", context.getCustomerId(), e);
context.setRejectReason("客户验证失败");
return false;
}
}
/**
* 验证理赔信息
*/
private boolean validateClaimInfo(ClaimContext context) {
// 验证理赔金额
if (context.getClaimAmount().compareTo(BigDecimal.ZERO) <= 0) {
context.setRejectReason("理赔金额必须大于0");
return false;
}
// 验证理赔金额是否超过保额
BigDecimal policyAmount = context.getPolicyInfo().getCoverageAmount();
if (context.getClaimAmount().compareTo(policyAmount) > 0) {
context.setRejectReason("理赔金额超过保险金额");
return false;
}
// 验证理赔类型
if (context.getClaimType() == null) {
context.setRejectReason("理赔类型不能为空");
return false;
}
return true;
}
/**
* 检查理赔时效
*/
private boolean checkClaimTimeliness(ClaimContext context) {
LocalDateTime incidentTime = context.getClaimDetails().getIncidentTime();
LocalDateTime claimTime = context.getClaimTime();
// 检查是否在规定时间内报案
long daysBetween = Duration.between(incidentTime, claimTime).toDays();
int maxReportDays = context.getPolicyInfo().getMaxReportDays();
if (daysBetween > maxReportDays) {
context.setRejectReason(String.format("超过报案时效,最晚应在事故发生后%d天内报案", maxReportDays));
return false;
}
return true;
}
}
文档验证处理器
java
/**
* 文档验证处理器
*/
@Component
@Slf4j
public class DocumentVerificationHandler extends AbstractClaimAuditHandler {
@Autowired
private DocumentService documentService;
@Autowired
private OcrService ocrService;
@Override
protected AuditResult executeAudit(ClaimContext context) throws Exception {
// 1. 检查文档完整性
if (!checkDocumentCompleteness(context)) {
return AuditResult.RETURNED;
}
// 2. 验证文档真实性
if (!verifyDocumentAuthenticity(context)) {
return AuditResult.REJECTED;
}
// 3. 提取文档信息
if (!extractDocumentInfo(context)) {
return AuditResult.ESCALATED;
}
return AuditResult.APPROVED;
}
@Override
protected String getHandlerName() {
return "文档验证";
}
@Override
protected int getTimeoutMinutes() {
return 45; // 45分钟内完成文档验证
}
/**
* 检查文档完整性
*/
private boolean checkDocumentCompleteness(ClaimContext context) {
List<String> requiredDocTypes = getRequiredDocumentTypes(context.getClaimType());
List<String> missingDocs = new ArrayList<>();
for (String docType : requiredDocTypes) {
boolean hasDoc = context.getDocuments().stream()
.anyMatch(doc -> doc.getDocumentType().equals(docType));
if (!hasDoc) {
missingDocs.add(docType);
}
}
if (!missingDocs.isEmpty()) {
context.setRejectReason("缺少必要文档:" + String.join(", ", missingDocs));
return false;
}
return true;
}
/**
* 验证文档真实性
*/
private boolean verifyDocumentAuthenticity(ClaimContext context) {
for (Document doc : context.getDocuments()) {
try {
// 检查文档格式
if (!documentService.isValidFormat(doc)) {
context.setRejectReason("文档格式不正确:" + doc.getDocumentName());
return false;
}
// 检查文档完整性
if (!documentService.isComplete(doc)) {
context.setRejectReason("文档不完整:" + doc.getDocumentName());
return false;
}
// 检查文档是否被篡改
if (documentService.isTampered(doc)) {
context.setRejectReason("文档可能被篡改:" + doc.getDocumentName());
return false;
}
context.getDocumentVerificationResults().put(doc.getDocumentId(), true);
} catch (Exception e) {
log.error("Failed to verify document: {}", doc.getDocumentId(), e);
context.getDocumentVerificationResults().put(doc.getDocumentId(), false);
context.setRejectReason("文档验证失败:" + doc.getDocumentName());
return false;
}
}
return true;
}
/**
* 提取文档信息
*/
private boolean extractDocumentInfo(ClaimContext context) {
Map<String, Object> extractedInfo = new HashMap<>();
for (Document doc : context.getDocuments()) {
try {
// 使用OCR提取文档信息
Map<String, Object> docInfo = ocrService.extractInfo(doc);
extractedInfo.putAll(docInfo);
// 验证提取的信息与申报信息是否一致
if (!validateExtractedInfo(context, doc, docInfo)) {
return false;
}
} catch (Exception e) {
log.error("Failed to extract info from document: {}", doc.getDocumentId(), e);
// OCR失败不一定拒绝,可能需要人工审核
context.setAuditData("ocrFailed", true);
}
}
context.setAuditData("extractedInfo", extractedInfo);
return true;
}
/**
* 验证提取的信息
*/
private boolean validateExtractedInfo(ClaimContext context, Document doc, Map<String, Object> docInfo) {
// 根据文档类型验证不同的信息
switch (doc.getDocumentType()) {
case "MEDICAL_REPORT":
return validateMedicalReport(context, docInfo);
case "INVOICE":
return validateInvoice(context, docInfo);
case "POLICE_REPORT":
return validatePoliceReport(context, docInfo);
default:
return true;
}
}
/**
* 获取必需文档类型
*/
private List<String> getRequiredDocumentTypes(ClaimType claimType) {
switch (claimType) {
case SIMPLE_MEDICAL:
return Arrays.asList("MEDICAL_REPORT", "INVOICE", "ID_CARD");
case COMPLEX_MEDICAL:
return Arrays.asList("MEDICAL_REPORT", "INVOICE", "ID_CARD", "DIAGNOSIS_REPORT");
case VEHICLE_DAMAGE:
return Arrays.asList("POLICE_REPORT", "DAMAGE_ASSESSMENT", "REPAIR_INVOICE");
default:
return Arrays.asList("ID_CARD", "CLAIM_FORM");
}
}
// 具体验证方法实现...
private boolean validateMedicalReport(ClaimContext context, Map<String, Object> docInfo) {
// 验证医疗报告信息
return true;
}
private boolean validateInvoice(ClaimContext context, Map<String, Object> docInfo) {
// 验证发票信息
return true;
}
private boolean validatePoliceReport(ClaimContext context, Map<String, Object> docInfo) {
// 验证警察报告信息
return true;
}
}
风险评估处理器
java
/**
* 风险评估处理器
*/
@Component
@Slf4j
public class RiskAssessmentHandler extends AbstractClaimAuditHandler {
@Autowired
private RiskEngineService riskEngineService;
@Autowired
private FraudDetectionService fraudDetectionService;
@Override
protected AuditResult executeAudit(ClaimContext context) throws Exception {
// 1. 计算风险评分
double riskScore = calculateRiskScore(context);
context.setAuditData("riskScore", riskScore);
// 2. 欺诈检测
List<String> fraudIndicators = detectFraud(context);
context.setAuditData("fraudIndicators", fraudIndicators);
// 3. 确定风险级别
RiskLevel riskLevel = determineRiskLevel(riskScore, fraudIndicators);
context.setRiskLevel(riskLevel);
// 4. 根据风险级别决定处理方式
return determineAuditResult(riskLevel, fraudIndicators);
}
@Override
protected String getHandlerName() {
return "风险评估";
}
@Override
protected int getTimeoutMinutes() {
return 20; // 20分钟内完成风险评估
}
/**
* 计算风险评分
*/
private double calculateRiskScore(ClaimContext context) {
RiskAssessmentRequest request = RiskAssessmentRequest.builder()
.claimId(context.getClaimId())
.customerId(context.getCustomerId())
.claimType(context.getClaimType())
.claimAmount(context.getClaimAmount())
.customerInfo(context.getCustomerInfo())
.policyInfo(context.getPolicyInfo())
.claimDetails(context.getClaimDetails())
.build();
return riskEngineService.calculateRiskScore(request);
}
/**
* 欺诈检测
*/
private List<String> detectFraud(ClaimContext context) {
List<String> indicators = new ArrayList<>();
// 1. 检查历史理赔记录
if (hasFrequentClaims(context)) {
indicators.add("频繁理赔");
}
// 2. 检查理赔金额异常
if (hasAbnormalAmount(context)) {
indicators.add("理赔金额异常");
}
// 3. 检查时间异常
if (hasTimeAnomalies(context)) {
indicators.add("时间异常");
}
// 4. 检查关联关系
if (hasRelationshipFraud(context)) {
indicators.add("关联欺诈");
}
// 5. 使用机器学习模型检测
List<String> mlIndicators = fraudDetectionService.detectFraud(context);
indicators.addAll(mlIndicators);
return indicators;
}
/**
* 确定风险级别
*/
private RiskLevel determineRiskLevel(double riskScore, List<String> fraudIndicators) {
if (!fraudIndicators.isEmpty() || riskScore >= 0.8) {
return RiskLevel.HIGH;
} else if (riskScore >= 0.6) {
return RiskLevel.MEDIUM;
} else {
return RiskLevel.LOW;
}
}
/**
* 根据风险级别决定审核结果
*/
private AuditResult determineAuditResult(RiskLevel riskLevel, List<String> fraudIndicators) {
if (riskLevel == RiskLevel.HIGH) {
if (!fraudIndicators.isEmpty()) {
return AuditResult.ESCALATED; // 有欺诈嫌疑,升级处理
} else {
return AuditResult.PENDING; // 高风险,需要进一步审核
}
} else {
return AuditResult.APPROVED; // 低中风险,可以继续自动审核
}
}
// 具体检测方法实现...
private boolean hasFrequentClaims(ClaimContext context) {
// 检查是否频繁理赔
return false;
}
private boolean hasAbnormalAmount(ClaimContext context) {
// 检查理赔金额是否异常
return false;
}
private boolean hasTimeAnomalies(ClaimContext context) {
// 检查时间是否异常
return false;
}
private boolean hasRelationshipFraud(ClaimContext context) {
// 检查是否存在关联欺诈
return false;
}
}
自动化审核处理器
java
/**
* 自动化审核处理器
*/
@Component
@Slf4j
public class AutomatedAuditHandler extends AbstractClaimAuditHandler {
@Autowired
private BusinessRuleEngine ruleEngine;
@Autowired
private ClaimCalculationService calculationService;
@Override
protected AuditResult executeAudit(ClaimContext context) throws Exception {
// 检查是否需要人工审核
if (context.requiresManualAudit()) {
return AuditResult.ESCALATED;
}
// 1. 应用业务规则
RuleExecutionResult ruleResult = applyBusinessRules(context);
if (!ruleResult.isSuccess()) {
context.setRejectReason(ruleResult.getFailureReason());
return AuditResult.REJECTED;
}
// 2. 计算理赔金额
BigDecimal calculatedAmount = calculateClaimAmount(context);
context.setApprovedAmount(calculatedAmount);
// 3. 最终验证
if (!performFinalValidation(context, calculatedAmount)) {
return AuditResult.REJECTED;
}
return AuditResult.APPROVED;
}
@Override
protected String getHandlerName() {
return "自动化审核";
}
@Override
protected boolean shouldContinue(AuditResult result) {
// 自动化审核如果通过,可以直接结束流程
return result != AuditResult.APPROVED;
}
/**
* 应用业务规则
*/
private RuleExecutionResult applyBusinessRules(ClaimContext context) {
RuleExecutionContext ruleContext = RuleExecutionContext.builder()
.claimType(context.getClaimType())
.claimAmount(context.getClaimAmount())
.policyInfo(context.getPolicyInfo())
.customerInfo(context.getCustomerInfo())
.riskLevel(context.getRiskLevel())
.extractedInfo(context.getAuditData())
.build();
return ruleEngine.executeRules(ruleContext);
}
/**
* 计算理赔金额
*/
private BigDecimal calculateClaimAmount(ClaimContext context) {
ClaimCalculationRequest request = ClaimCalculationRequest.builder()
.claimType(context.getClaimType())
.requestedAmount(context.getClaimAmount())
.policyInfo(context.getPolicyInfo())
.claimDetails(context.getClaimDetails())
.extractedInfo(context.getAuditData())
.build();
return calculationService.calculateAmount(request);
}
/**
* 最终验证
*/
private boolean performFinalValidation(ClaimContext context, BigDecimal calculatedAmount) {
// 验证计算金额是否合理
if (calculatedAmount.compareTo(BigDecimal.ZERO) <= 0) {
context.setRejectReason("计算的理赔金额无效");
return false;
}
// 验证是否超过保额
if (calculatedAmount.compareTo(context.getPolicyInfo().getCoverageAmount()) > 0) {
context.setRejectReason("理赔金额超过保险金额");
return false;
}
// 验证免赔额
BigDecimal deductible = context.getPolicyInfo().getDeductible();
if (calculatedAmount.compareTo(deductible) <= 0) {
context.setRejectReason("理赔金额未超过免赔额");
return false;
}
return true;
}
}
人工审核处理器
java
/**
* 人工审核处理器
*/
@Component
@Slf4j
public class ManualAuditHandler extends AbstractClaimAuditHandler {
@Autowired
private AuditorAssignmentService assignmentService;
@Autowired
private NotificationService notificationService;
@Override
protected AuditResult executeAudit(ClaimContext context) throws Exception {
// 1. 分配审核员
String auditorId = assignAuditor(context);
if (auditorId == null) {
return AuditResult.ESCALATED;
}
context.setCurrentAuditor(auditorId);
// 2. 发送审核通知
sendAuditNotification(context, auditorId);
// 3. 等待人工审核结果
return AuditResult.PENDING;
}
@Override
protected String getHandlerName() {
return "人工审核";
}
@Override
protected boolean isAutomated() {
return false;
}
@Override
protected int getTimeoutMinutes() {
return 480; // 8小时内完成人工审核
}
/**
* 分配审核员
*/
private String assignAuditor(ClaimContext context) {
AuditorAssignmentRequest request = AuditorAssignmentRequest.builder()
.claimId(context.getClaimId())
.claimType(context.getClaimType())
.claimAmount(context.getClaimAmount())
.riskLevel(context.getRiskLevel())
.requiredLevel(context.getRequiredAuditLevel())
.urgency(calculateUrgency(context))
.build();
return assignmentService.assignAuditor(request);
}
/**
* 发送审核通知
*/
private void sendAuditNotification(ClaimContext context, String auditorId) {
AuditNotification notification = AuditNotification.builder()
.auditorId(auditorId)
.claimId(context.getClaimId())
.claimType(context.getClaimType())
.claimAmount(context.getClaimAmount())
.riskLevel(context.getRiskLevel())
.deadline(context.getAuditDeadline())
.priority(calculatePriority(context))
.build();
notificationService.sendAuditNotification(notification);
}
/**
* 计算紧急程度
*/
private UrgencyLevel calculateUrgency(ClaimContext context) {
if (context.getRiskLevel() == RiskLevel.HIGH) {
return UrgencyLevel.HIGH;
}
if (context.getClaimAmount().compareTo(new BigDecimal("100000")) > 0) {
return UrgencyLevel.HIGH;
}
return UrgencyLevel.NORMAL;
}
/**
* 计算优先级
*/
private Priority calculatePriority(ClaimContext context) {
if (context.getRiskLevel() == RiskLevel.HIGH) {
return Priority.HIGH;
}
if (context.getClaimAmount().compareTo(new BigDecimal("50000")) > 0) {
return Priority.MEDIUM;
}
return Priority.LOW;
}
}
多级审核权限控制
1. 权限管理系统
java
/**
* 审核权限管理服务
*/
@Service
public class AuditPermissionService {
@Autowired
private UserService userService;
/**
* 检查审核权限
*/
public boolean hasAuditPermission(String auditorId, ClaimContext context) {
AuditorInfo auditor = userService.getAuditorInfo(auditorId);
if (auditor == null || !auditor.isActive()) {
return false;
}
// 检查审核级别权限
if (!hasLevelPermission(auditor, context.getRequiredAuditLevel())) {
return false;
}
// 检查金额权限
if (!hasAmountPermission(auditor, context.getClaimAmount())) {
return false;
}
// 检查险种权限
if (!hasClaimTypePermission(auditor, context.getClaimType())) {
return false;
}
return true;
}
/**
* 检查级别权限
*/
private boolean hasLevelPermission(AuditorInfo auditor, AuditLevel requiredLevel) {
return auditor.getAuditLevel().ordinal() >= requiredLevel.ordinal();
}
/**
* 检查金额权限
*/
private boolean hasAmountPermission(AuditorInfo auditor, BigDecimal claimAmount) {
return claimAmount.compareTo(auditor.getMaxAuditAmount()) <= 0;
}
/**
* 检查险种权限
*/
private boolean hasClaimTypePermission(AuditorInfo auditor, ClaimType claimType) {
return auditor.getAuthorizedClaimTypes().contains(claimType);
}
/**
* 获取可用审核员列表
*/
public List<AuditorInfo> getAvailableAuditors(ClaimContext context) {
return userService.getAllAuditors().stream()
.filter(auditor -> hasAuditPermission(auditor.getId(), context))
.filter(AuditorInfo::isAvailable)
.sorted(Comparator.comparing(AuditorInfo::getWorkload))
.collect(Collectors.toList());
}
}
/**
* 审核员信息
*/
@Data
@Builder
public class AuditorInfo {
private String id;
private String name;
private AuditLevel auditLevel;
private BigDecimal maxAuditAmount;
private Set<ClaimType> authorizedClaimTypes;
private boolean active;
private boolean available;
private int workload;
private LocalDateTime lastActiveTime;
}
2. 审核员分配策略
java
/**
* 审核员分配服务
*/
@Service
public class AuditorAssignmentService {
@Autowired
private AuditPermissionService permissionService;
@Autowired
private WorkloadBalancer workloadBalancer;
/**
* 分配审核员
*/
public String assignAuditor(AuditorAssignmentRequest request) {
// 1. 获取可用审核员列表
List<AuditorInfo> availableAuditors = permissionService
.getAvailableAuditors(createClaimContext(request));
if (availableAuditors.isEmpty()) {
log.warn("No available auditors for claim: {}", request.getClaimId());
return null;
}
// 2. 根据策略选择审核员
AuditorInfo selectedAuditor = selectAuditor(availableAuditors, request);
// 3. 分配审核任务
if (assignAuditTask(selectedAuditor.getId(), request)) {
return selectedAuditor.getId();
}
return null;
}
/**
* 选择审核员
*/
private AuditorInfo selectAuditor(List<AuditorInfo> auditors, AuditorAssignmentRequest request) {
// 根据紧急程度和工作负载选择
if (request.getUrgency() == UrgencyLevel.HIGH) {
// 高紧急度:选择级别最高的可用审核员
return auditors.stream()
.max(Comparator.comparing(AuditorInfo::getAuditLevel))
.orElse(auditors.get(0));
} else {
// 普通紧急度:负载均衡
return workloadBalancer.selectByWorkload(auditors);
}
}
/**
* 分配审核任务
*/
private boolean assignAuditTask(String auditorId, AuditorAssignmentRequest request) {
try {
AuditTask task = AuditTask.builder()
.taskId(UUID.randomUUID().toString())
.claimId(request.getClaimId())
.auditorId(auditorId)
.assignTime(LocalDateTime.now())
.deadline(calculateDeadline(request))
.priority(calculatePriority(request))
.build();
// 保存任务到数据库
auditTaskRepository.save(task);
// 更新审核员工作负载
workloadBalancer.incrementWorkload(auditorId);
return true;
} catch (Exception e) {
log.error("Failed to assign audit task to auditor: {}", auditorId, e);
return false;
}
}
private LocalDateTime calculateDeadline(AuditorAssignmentRequest request) {
int hours = request.getUrgency() == UrgencyLevel.HIGH ? 4 : 8;
return LocalDateTime.now().plusHours(hours);
}
private Priority calculatePriority(AuditorAssignmentRequest request) {
if (request.getUrgency() == UrgencyLevel.HIGH) {
return Priority.HIGH;
}
return Priority.MEDIUM;
}
}
自动化审核与人工审核的切换机制
1. 智能路由决策
java
/**
* 审核路由决策服务
*/
@Service
public class AuditRoutingService {
@Autowired
private RiskAssessmentService riskService;
@Autowired
private BusinessRuleService ruleService;
/**
* 决定审核路由
*/
public AuditRoute determineAuditRoute(ClaimContext context) {
// 1. 基于规则的路由决策
AuditRoute ruleBasedRoute = determineByRules(context);
if (ruleBasedRoute == AuditRoute.MANUAL_AUDIT) {
return ruleBasedRoute;
}
// 2. 基于风险评估的路由决策
AuditRoute riskBasedRoute = determineByRisk(context);
if (riskBasedRoute == AuditRoute.MANUAL_AUDIT) {
return riskBasedRoute;
}
// 3. 基于机器学习模型的路由决策
AuditRoute mlBasedRoute = determineByML(context);
return mlBasedRoute;
}
/**
* 基于规则的路由决策
*/
private AuditRoute determineByRules(ClaimContext context) {
// 大额案件必须人工审核
if (context.getClaimAmount().compareTo(new BigDecimal("50000")) > 0) {
return AuditRoute.MANUAL_AUDIT;
}
// 复杂案件类型必须人工审核
if (isComplexClaimType(context.getClaimType())) {
return AuditRoute.MANUAL_AUDIT;
}
// 特殊客户必须人工审核
if (isVipCustomer(context.getCustomerId())) {
return AuditRoute.MANUAL_AUDIT;
}
return AuditRoute.AUTOMATED_AUDIT;
}
/**
* 基于风险评估的路由决策
*/
private AuditRoute determineByRisk(ClaimContext context) {
double riskScore = riskService.calculateRiskScore(context);
if (riskScore >= 0.7) {
return AuditRoute.MANUAL_AUDIT;
} else if (riskScore >= 0.4) {
return AuditRoute.HYBRID_AUDIT; // 混合审核
} else {
return AuditRoute.AUTOMATED_AUDIT;
}
}
/**
* 基于机器学习模型的路由决策
*/
private AuditRoute determineByML(ClaimContext context) {
// 使用训练好的模型预测最佳审核路由
MLPredictionRequest request = MLPredictionRequest.builder()
.claimFeatures(extractClaimFeatures(context))
.customerFeatures(extractCustomerFeatures(context))
.policyFeatures(extractPolicyFeatures(context))
.build();
MLPredictionResult result = mlModelService.predict(request);
return mapPredictionToRoute(result);
}
private boolean isComplexClaimType(ClaimType claimType) {
return claimType == ClaimType.COMPLEX_MEDICAL ||
claimType == ClaimType.MAJOR_ACCIDENT;
}
private boolean isVipCustomer(String customerId) {
// 检查是否为VIP客户
return false;
}
}
/**
* 审核路由枚举
*/
public enum AuditRoute {
AUTOMATED_AUDIT("自动化审核"),
MANUAL_AUDIT("人工审核"),
HYBRID_AUDIT("混合审核");
private final String description;
AuditRoute(String description) {
this.description = description;
}
}
2. 混合审核模式
java
/**
* 混合审核处理器
*/
@Component
public class HybridAuditHandler extends AbstractClaimAuditHandler {
@Autowired
private AutomatedAuditHandler automatedHandler;
@Autowired
private ManualAuditHandler manualHandler;
@Override
protected AuditResult executeAudit(ClaimContext context) throws Exception {
// 1. 先进行自动化审核
AuditResult automatedResult = performAutomatedAudit(context);
// 2. 根据自动化审核结果决定是否需要人工复核
if (needsManualReview(context, automatedResult)) {
return performManualReview(context, automatedResult);
}
return automatedResult;
}
@Override
protected String getHandlerName() {
return "混合审核";
}
/**
* 执行自动化审核
*/
private AuditResult performAutomatedAudit(ClaimContext context) {
try {
Context<ClaimContext, AuditResult> automatedContext = new DefaultContext<>(context);
automatedHandler.handle(automatedContext);
return automatedContext.getResponse();
} catch (Exception e) {
log.error("Automated audit failed, escalating to manual", e);
return AuditResult.ESCALATED;
}
}
/**
* 判断是否需要人工复核
*/
private boolean needsManualReview(ClaimContext context, AuditResult automatedResult) {
// 自动化审核失败或升级
if (automatedResult == AuditResult.ESCALATED || automatedResult == AuditResult.REJECTED) {
return true;
}
// 置信度不够
Double confidence = context.getAuditData("confidence", Double.class);
if (confidence != null && confidence < 0.8) {
return true;
}
// 金额较大需要复核
if (context.getApprovedAmount().compareTo(new BigDecimal("20000")) > 0) {
return true;
}
return false;
}
/**
* 执行人工复核
*/
private AuditResult performManualReview(ClaimContext context, AuditResult automatedResult) {
// 设置复核标记
context.setAuditData("isReview", true);
context.setAuditData("automatedResult", automatedResult);
try {
Context<ClaimContext, AuditResult> manualContext = new DefaultContext<>(context);
manualHandler.handle(manualContext);
return manualContext.getResponse();
} catch (Exception e) {
log.error("Manual review failed", e);
return AuditResult.ESCALATED;
}
}
}
性能优化建议
1. 异步处理优化
java
/**
* 异步审核服务
*/
@Service
public class AsyncAuditService {
@Autowired
private ClaimAuditService auditService;
@Async("auditExecutor")
public CompletableFuture<AuditResult> processClaimAsync(ClaimContext context) {
try {
AuditResult result = auditService.processClaimAudit(context);
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
CompletableFuture<AuditResult> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
/**
* 批量异步处理
*/
public CompletableFuture<List<AuditResult>> processBatchAsync(List<ClaimContext> contexts) {
List<CompletableFuture<AuditResult>> futures = contexts.stream()
.map(this::processClaimAsync)
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
}
2. 缓存优化
java
/**
* 审核缓存服务
*/
@Service
public class AuditCacheService {
@Cacheable(value = "policyInfo", key = "#policyId")
public PolicyInfo getPolicyInfo(String policyId) {
return policyService.getPolicyInfo(policyId);
}
@Cacheable(value = "customerInfo", key = "#customerId")
public CustomerInfo getCustomerInfo(String customerId) {
return customerService.getCustomerInfo(customerId);
}
@Cacheable(value = "riskScore", key = "#customerId + '_' + #claimType")
public Double getRiskScore(String customerId, ClaimType claimType) {
return riskService.calculateRiskScore(customerId, claimType);
}
}
常见问题解决方案
1. 审核超时处理
java
/**
* 审核超时监控服务
*/
@Service
public class AuditTimeoutMonitorService {
@Scheduled(fixedRate = 300000) // 每5分钟检查一次
public void checkTimeoutClaims() {
List<ClaimContext> timeoutClaims = getTimeoutClaims();
for (ClaimContext claim : timeoutClaims) {
handleTimeoutClaim(claim);
}
}
private void handleTimeoutClaim(ClaimContext claim) {
// 1. 记录超时事件
recordTimeoutEvent(claim);
// 2. 自动升级处理
escalateTimeoutClaim(claim);
// 3. 发送通知
sendTimeoutNotification(claim);
}
private void escalateTimeoutClaim(ClaimContext claim) {
// 升级到更高级别的审核员
AuditLevel currentLevel = claim.getRequiredAuditLevel();
AuditLevel nextLevel = getNextLevel(currentLevel);
// 重新分配审核员
String newAuditor = assignmentService.assignAuditor(
claim.getClaimId(), nextLevel);
if (newAuditor != null) {
claim.setCurrentAuditor(newAuditor);
// 延长审核时间
claim.setAuditDeadline(LocalDateTime.now().plusHours(4));
}
}
}
2. 并发审核冲突处理
java
/**
* 审核锁管理服务
*/
@Service
public class AuditLockService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 获取审核锁
*/
public boolean acquireAuditLock(String claimId, String auditorId) {
String lockKey = "audit_lock:" + claimId;
String lockValue = auditorId + ":" + System.currentTimeMillis();
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, Duration.ofHours(2));
return Boolean.TRUE.equals(acquired);
}
/**
* 释放审核锁
*/
public void releaseAuditLock(String claimId, String auditorId) {
String lockKey = "audit_lock:" + claimId;
String lockValue = redisTemplate.opsForValue().get(lockKey);
if (lockValue != null && lockValue.startsWith(auditorId + ":")) {
redisTemplate.delete(lockKey);
}
}
}
总结
通过责任链模式实现的保险理赔线上审核系统具有以下优势:
- 流程标准化:将复杂的审核流程分解为标准化的处理步骤
- 权限精确控制:多级审核权限确保审核质量和合规性
- 智能路由:自动化与人工审核的智能切换,提高效率
- 风险控制:内置风险评估和欺诈检测机制
- 可追溯性:完整的审核记录便于监管和质量控制
- 高可用性:支持并发处理和故障恢复
在实际应用中,需要根据具体的业务规则和监管要求进行相应的调整和优化。