企业级Java项目金融应用领域——保险系统

1. 保单管理

保单创建与维护(新保、续保、批改)

保单查询与统计

保单状态跟踪(生效、终止、注销)

保费计算引擎

yml 复制代码
server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/insurance_db?useSSL=false&serverTimezone=UTC
    username: insurance_user
    password: securepassword
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  cache:
    type: redis
  redis:
    host: localhost
    port: 6379
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

seata:
  enabled: true
  application-id: insurance-policy-service
  tx-service-group: insurance_tx_group
  service:
    vgroup-mapping:
      insurance_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      group: SEATA_GROUP
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      group: SEATA_GROUP

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 30000
      pool-name: InsuranceHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

server:
  tomcat:
    max-threads: 200
    min-spare-threads: 10
    accept-count: 100
java 复制代码
/**
	保单主表
*/
@Entity
@Table(name = "policy")
public class Policy {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String policyNo; // 保单号
    
    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product; // 保险产品
    
    @ManyToOne
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer; // 投保人
    
    @Column(nullable = false)
    private Date effectiveDate; // 生效日期
    
    @Column(nullable = false)
    private Date expiryDate; // 失效日期
    
    @Column(nullable = false)
    private BigDecimal premium; // 保费
    
    @Column(nullable = false)
    private BigDecimal sumInsured; // 保额
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private PolicyStatus status; // 保单状态
    
    @OneToMany(mappedBy = "policy", cascade = CascadeType.ALL)
    private List<PolicyBeneficiary> beneficiaries; // 受益人
    
    @OneToMany(mappedBy = "policy", cascade = CascadeType.ALL)
    private List<PolicyRider> riders; // 附加险
    
    @Column(nullable = false)
    private Date createdAt;
    
    @Column(nullable = false)
    private Date updatedAt;
    
    // Getters and Setters
}
java 复制代码
public enum PolicyStatus {
    DRAFT,          // 草稿
    PENDING,        // 待核保
    UNDER_REVIEW,   // 核保中
    ACTIVE,         // 生效中
    LAPSED,         // 已失效
    CANCELLED,      // 已退保
    CLAIMED         // 已理赔
}
java 复制代码
@Data
public class PolicyDTO {
    private Long id;
    
    @NotBlank
    private String policyNo;
    
    @NotNull
    private Long productId;
    
    @NotNull
    private Long customerId;
    
    @NotNull
    @Future
    private Date effectiveDate;
    
    @NotNull
    @Future
    private Date expiryDate;
    
    @NotNull
    @DecimalMin("0.00")
    private BigDecimal premium;
    
    @NotNull
    @DecimalMin("0.00")
    private BigDecimal sumInsured;
    
    private PolicyStatus status;
    
    private List<BeneficiaryDTO> beneficiaries;
    private List<PolicyRiderDTO> riders;
}
java 复制代码
/**
	保单号生成器
*/
@Component
public class PolicyNumberGenerator {
    
    private static final String PREFIX = "POL";
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    private static int sequence = 0;
    private static final int MAX_SEQUENCE = 9999;
    
    public synchronized String generatePolicyNo(String productCode) {
        String datePart = dateFormat.format(new Date());
        
        if (sequence >= MAX_SEQUENCE) {
            sequence = 0;
        }
        sequence++;
        
        String sequencePart = String.format("%04d", sequence);
        
        return PREFIX + productCode + datePart + sequencePart;
    }
}
java 复制代码
@Configuration
@EnableTransactionManagement
@EnableAutoDataSourceProxy
public class SeataConfig {
    // Seata自动配置
}
java 复制代码
@Configuration
public class RabbitMQConfig {
    
    @Bean
    public Queue policyQueue() {
        return new Queue("policy.queue", true);
    }
    
    @Bean
    public Queue underwritingQueue() {
        return new Queue("underwriting.queue", true);
    }
}
java 复制代码
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(60))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}
java 复制代码
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtRequestFilter jwtRequestFilter;
    
    public SecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
                         JwtRequestFilter jwtRequestFilter) {
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtRequestFilter = jwtRequestFilter;
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .anyRequest().authenticated()
            .and()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
java 复制代码
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiResponse> handleResourceNotFound(ResourceNotFoundException ex) {
        log.warn("Resource not found: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(ApiResponse.error(HttpStatus.NOT_FOUND.value(), ex.getMessage()));
    }
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse> handleBusinessException(BusinessException ex) {
        log.error("Business exception: {}", ex.getMessage(), ex);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), ex.getMessage()));
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
        String errorMessage = ex.getBindingResult().getFieldErrors().stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.joining("; "));
        
        log.warn("Validation error: {}", errorMessage);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), errorMessage));
    }
    
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ApiResponse> handleConstraintViolation(ConstraintViolationException ex) {
        String errorMessage = ex.getConstraintViolations().stream()
                .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
                .collect(Collectors.joining("; "));
        
        log.warn("Constraint violation: {}", errorMessage);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), errorMessage));
    }
    
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<ApiResponse> handleAccessDenied(AccessDeniedException ex) {
        log.warn("Access denied: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body(ApiResponse.error(HttpStatus.FORBIDDEN.value(), "无权访问该资源"));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse> handleAllExceptions(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), 
                        "系统内部错误,请联系管理员"));
    }
}

Controller层

java 复制代码
@RestController
@RequestMapping("/api/policies")
@Api(tags = "保单管理")
public class PolicyController {
    
    private final PolicyService policyService;
    
    public PolicyController(PolicyService policyService) {
        this.policyService = policyService;
    }
    
    @PostMapping
    @ApiOperation("创建保单")
    @PreAuthorize("hasRole('UNDERWRITER') or hasRole('ADMIN')")
    public ResponseEntity<Policy> createPolicy(@RequestBody PolicyDTO policyDTO) {
        Policy policy = policyService.createPolicy(policyDTO);
        return ResponseEntity.ok(policy);
    }
    
    @PutMapping("/{id}")
    @ApiOperation("更新保单")
    @PreAuthorize("hasRole('UNDERWRITER') or hasRole('ADMIN')")
    public ResponseEntity<Policy> updatePolicy(@PathVariable Long id, @RequestBody PolicyDTO policyDTO) {
        Policy policy = policyService.updatePolicy(id, policyDTO);
        return ResponseEntity.ok(policy);
    }
    
    @DeleteMapping("/{id}")
    @ApiOperation("删除保单")
    @PreAuthorize("hasRole('UNDERWRITER') or hasRole('ADMIN')")
    public ResponseEntity<Void> deletePolicy(@PathVariable Long id) {
        policyService.deletePolicy(id);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取保单详情")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Policy> getPolicyById(@PathVariable Long id) {
        Policy policy = policyService.getPolicyById(id);
        return ResponseEntity.ok(policy);
    }
    
    @GetMapping("/number/{policyNo}")
    @ApiOperation("通过保单号获取保单")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Policy> getPolicyByNo(@PathVariable String policyNo) {
        Policy policy = policyService.getPolicyByNo(policyNo);
        return ResponseEntity.ok(policy);
    }
    
    @GetMapping
    @ApiOperation("获取所有保单(分页)")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Page<Policy>> getAllPolicies(Pageable pageable) {
        Page<Policy> policies = policyService.getAllPolicies(pageable);
        return ResponseEntity.ok(policies);
    }
    
    @GetMapping("/customer/{customerId}")
    @ApiOperation("获取客户的所有保单")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<List<Policy>> getPoliciesByCustomer(@PathVariable Long customerId) {
        List<Policy> policies = policyService.getPoliciesByCustomer(customerId);
        return ResponseEntity.ok(policies);
    }
    
    @GetMapping("/status/{status}")
    @ApiOperation("根据状态获取保单")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<List<Policy>> getPoliciesByStatus(@PathVariable String status) {
        List<Policy> policies = policyService.getPoliciesByStatus(status);
        return ResponseEntity.ok(policies);
    }
    
    @GetMapping("/active")
    @ApiOperation("获取指定日期的有效保单")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<List<Policy>> getActivePolicies(@RequestParam(required = false) Date date) {
        if (date == null) {
            date = new Date();
        }
        List<Policy> policies = policyService.getActivePolicies(date);
        return ResponseEntity.ok(policies);
    }
    
    @PutMapping("/{id}/status/{status}")
    @ApiOperation("变更保单状态")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Policy> changePolicyStatus(@PathVariable Long id, @PathVariable String status) {
        Policy policy = policyService.changePolicyStatus(id, status);
        return ResponseEntity.ok(policy);
    }
    
    @PostMapping("/calculate-premium")
    @ApiOperation("计算保费")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'ADMIN')")
    public ResponseEntity<BigDecimal> calculatePremium(@RequestBody PolicyDTO policyDTO) {
        BigDecimal premium = policyService.calculatePremium(policyDTO);
        return ResponseEntity.ok(premium);
    }
}

Service层

java 复制代码
public interface PolicyService {
    Policy createPolicy(PolicyDTO policyDTO);
    Policy updatePolicy(Long id, PolicyDTO policyDTO);
    void deletePolicy(Long id);
    Policy getPolicyById(Long id);
    Policy getPolicyByNo(String policyNo);
    Page<Policy> getAllPolicies(Pageable pageable);
    List<Policy> getPoliciesByCustomer(Long customerId);
    List<Policy> getPoliciesByStatus(String status);
    List<Policy> getActivePolicies(Date date);
    Policy changePolicyStatus(Long id, String status);
    BigDecimal calculatePremium(PolicyDTO policyDTO);
}
java 复制代码
@Service
@Transactional
public class PolicyServiceImpl implements PolicyService {
    
    private final PolicyRepository policyRepository;
    private final ProductRepository productRepository;
    private final CustomerRepository customerRepository;
    private final PolicyBeneficiaryRepository beneficiaryRepository;
    private final PolicyRiderRepository riderRepository;
    private final ModelMapper modelMapper;
    private final RabbitTemplate rabbitTemplate;
    private final PolicyNumberGenerator policyNumberGenerator;
    
    public PolicyServiceImpl(PolicyRepository policyRepository, 
                           ProductRepository productRepository,
                           CustomerRepository customerRepository,
                           PolicyBeneficiaryRepository beneficiaryRepository,
                           PolicyRiderRepository riderRepository,
                           ModelMapper modelMapper,
                           RabbitTemplate rabbitTemplate,
                           PolicyNumberGenerator policyNumberGenerator) {
        this.policyRepository = policyRepository;
        this.productRepository = productRepository;
        this.customerRepository = customerRepository;
        this.beneficiaryRepository = beneficiaryRepository;
        this.riderRepository = riderRepository;
        this.modelMapper = modelMapper;
        this.rabbitTemplate = rabbitTemplate;
        this.policyNumberGenerator = policyNumberGenerator;
    }

	@Override
    @GlobalTransactional
    public Policy createPolicy(PolicyDTO policyDTO) {
        // 验证产品
        Product product = productRepository.findById(policyDTO.getProductId())
                .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
        
        // 验证客户
        Customer customer = customerRepository.findById(policyDTO.getCustomerId())
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found"));
        
        // 生成保单号
        String policyNo = policyNumberGenerator.generatePolicyNo(product.getCode());
        
        // 计算保费
        BigDecimal premium = premiumCalculationEngine.calculatePremium(policyDTO, product);
        policyDTO.setPremium(premium);
        
        // 创建保单
        Policy policy = modelMapper.map(policyDTO, Policy.class);
        policy.setPolicyNo(policyNo);
        policy.setProduct(product);
        policy.setCustomer(customer);
        policy.setStatus(PolicyStatus.PENDING);
        policy.setUnderwritingStatus(UnderwritingStatus.PENDING);
        policy.setCreatedAt(new Date());
        policy.setUpdatedAt(new Date());
        
        // 保存保单
        Policy savedPolicy = policyRepository.save(policy);
        
        // 保存受益人
        saveBeneficiaries(policyDTO, savedPolicy);
        
        // 保存附加险
        saveRiders(policyDTO, savedPolicy);
        
        // 记录审计日志
        auditLogService.logPolicyCreation(savedPolicy);
        
        // 异步执行核保
        performUnderwritingAsync(savedPolicy);
        
        return savedPolicy;
    }

    @Async
    protected void performUnderwritingAsync(Policy policy) {
        try {
            UnderwritingStatus status = underwritingService.performAutoUnderwriting(policy);
            
            if (status == UnderwritingStatus.APPROVED) {
                // 自动核保通过,准备支付
                preparePayment(policy);
            } else if (status == UnderwritingStatus.REFERRED) {
                // 需要人工核保
                underwritingService.initiateManualUnderwriting(policy.getId());
            }
        } catch (Exception e) {
            log.error("Underwriting failed for policy: " + policy.getPolicyNo(), e);
        }
    }

    private void preparePayment(Policy policy) {
        PaymentRequest paymentRequest = new PaymentRequest();
        paymentRequest.setPolicyNo(policy.getPolicyNo());
        paymentRequest.setAmount(policy.getPremium());
        paymentRequest.setOrderId("PAY-" + policy.getPolicyNo());
        
        PaymentResult result = paymentService.initiatePayment(paymentRequest);
        
        if (result.isSuccess()) {
            policy.setPaymentStatus(PaymentStatus.PENDING);
            policyRepository.save(policy);
            
            // 发送支付通知
            rabbitTemplate.convertAndSend("payment.notification.queue", policy.getId());
        }
    }

    private void saveRiders(PolicyDTO policyDTO, Policy savedPolicy) {
        if (policyDTO.getRiders() != null && !policyDTO.getRiders().isEmpty()) {
            List<PolicyRider> riders = policyDTO.getRiders().stream()
                    .map(r -> {
                        PolicyRider rider = modelMapper.map(r, PolicyRider.class);
                        rider.setPolicy(savedPolicy);
                        return rider;
                    })
                    .collect(Collectors.toList());
            riderRepository.saveAll(riders);
        }
    }

    private void saveBeneficiaries(PolicyDTO policyDTO, Policy savedPolicy) {
        if (policyDTO.getBeneficiaries() != null && !policyDTO.getBeneficiaries().isEmpty()) {
            List<PolicyBeneficiary> beneficiaries = policyDTO.getBeneficiaries().stream()
                    .map(b -> {
                        PolicyBeneficiary beneficiary = modelMapper.map(b, PolicyBeneficiary.class);
                        beneficiary.setPolicy(savedPolicy);
                        return beneficiary;
                    })
                    .collect(Collectors.toList());
            beneficiaryRepository.saveAll(beneficiaries);
        }
    }
    @Override
    public Policy createPolicy(PolicyDTO policyDTO) {
        // 验证产品是否存在
        Product product = productRepository.findById(policyDTO.getProductId())
                .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
        
        // 验证客户是否存在
        Customer customer = customerRepository.findById(policyDTO.getCustomerId())
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found"));
        
        // 生成保单号
        String policyNo = policyNumberGenerator.generatePolicyNo(product.getCode());
        
        // 创建保单
        Policy policy = modelMapper.map(policyDTO, Policy.class);
        policy.setPolicyNo(policyNo);
        policy.setProduct(product);
        policy.setCustomer(customer);
        policy.setStatus(PolicyStatus.DRAFT);
        policy.setCreatedAt(new Date());
        policy.setUpdatedAt(new Date());
        
        // 保存保单
        Policy savedPolicy = policyRepository.save(policy);
        
        // 保存受益人
        if (policyDTO.getBeneficiaries() != null && !policyDTO.getBeneficiaries().isEmpty()) {
            List<PolicyBeneficiary> beneficiaries = policyDTO.getBeneficiaries().stream()
                    .map(b -> {
                        PolicyBeneficiary beneficiary = modelMapper.map(b, PolicyBeneficiary.class);
                        beneficiary.setPolicy(savedPolicy);
                        return beneficiary;
                    })
                    .collect(Collectors.toList());
            beneficiaryRepository.saveAll(beneficiaries);
        }
        
        // 保存附加险
        if (policyDTO.getRiders() != null && !policyDTO.getRiders().isEmpty()) {
            List<PolicyRider> riders = policyDTO.getRiders().stream()
                    .map(r -> {
                        PolicyRider rider = modelMapper.map(r, PolicyRider.class);
                        rider.setPolicy(savedPolicy);
                        return rider;
                    })
                    .collect(Collectors.toList());
            riderRepository.saveAll(riders);
        }
        
        // 发送消息到队列,触发后续流程
        rabbitTemplate.convertAndSend("policy.queue", savedPolicy.getId());
        
        return savedPolicy;
    }

    @Override
    @CacheEvict(value = "policies", key = "#id")
    public Policy updatePolicy(Long id, PolicyDTO policyDTO) {
        Policy existingPolicy = policyRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Policy not found"));
        
        // 检查是否可以修改
        if (!existingPolicy.getStatus().equals(PolicyStatus.DRAFT)) {
            throw new IllegalStateException("Only draft policies can be modified");
        }
        
        modelMapper.map(policyDTO, existingPolicy);
        existingPolicy.setUpdatedAt(new Date());
        
        // 更新受益人
        if (policyDTO.getBeneficiaries() != null) {
            // 删除现有受益人
            beneficiaryRepository.deleteByPolicyId(id);
            
            // 添加新的受益人
            List<PolicyBeneficiary> beneficiaries = policyDTO.getBeneficiaries().stream()
                    .map(b -> {
                        PolicyBeneficiary beneficiary = modelMapper.map(b, PolicyBeneficiary.class);
                        beneficiary.setPolicy(existingPolicy);
                        return beneficiary;
                    })
                    .collect(Collectors.toList());
            beneficiaryRepository.saveAll(beneficiaries);
        }
        
        // 更新附加险
        if (policyDTO.getRiders() != null) {
            // 删除现有附加险
            riderRepository.deleteByPolicyId(id);
            
            // 添加新的附加险
            List<PolicyRider> riders = policyDTO.getRiders().stream()
                    .map(r -> {
                        PolicyRider rider = modelMapper.map(r, PolicyRider.class);
                        rider.setPolicy(existingPolicy);
                        return rider;
                    })
                    .collect(Collectors.toList());
            riderRepository.saveAll(riders);
        }
        
        return policyRepository.save(existingPolicy);
    }

    @Override
    @CacheEvict(value = "policies", key = "#id")
    public void deletePolicy(Long id) {
        Policy policy = policyRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Policy not found"));
        
        // 检查是否可以删除
        if (!policy.getStatus().equals(PolicyStatus.DRAFT)) {
            throw new IllegalStateException("Only draft policies can be deleted");
        }
        
        policyRepository.delete(policy);
    }

    @Override
    @Cacheable(value = "policies", key = "#id")
    public Policy getPolicyById(Long id) {
        return policyRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Policy not found"));
    }

    @Override
    @Cacheable(value = "policies", key = "#policyNo")
    public Policy getPolicyByNo(String policyNo) {
        return policyRepository.findByPolicyNo(policyNo)
                .orElseThrow(() -> new ResourceNotFoundException("Policy not found"));
    }

    @Override
    public Page<Policy> getAllPolicies(Pageable pageable) {
        return policyRepository.findAll(pageable);
    }

    @Override
    public List<Policy> getPoliciesByCustomer(Long customerId) {
        return policyRepository.findByCustomerId(customerId);
    }

    @Override
    public List<Policy> getPoliciesByStatus(String status) {
        PolicyStatus policyStatus = PolicyStatus.valueOf(status.toUpperCase());
        return policyRepository.findByStatus(policyStatus);
    }

    @Override
    public List<Policy> getActivePolicies(Date date) {
        return policyRepository.findActivePolicies(date);
    }

    @Override
    @CacheEvict(value = "policies", key = "#id")
    public Policy changePolicyStatus(Long id, String status) {
        Policy policy = policyRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Policy not found"));
        
        PolicyStatus newStatus = PolicyStatus.valueOf(status.toUpperCase());
        
        // 验证状态转换是否合法
        validateStatusTransition(policy.getStatus(), newStatus);
        
        policy.setStatus(newStatus);
        policy.setUpdatedAt(new Date());
        
        // 如果是激活状态,记录激活时间
        if (newStatus == PolicyStatus.ACTIVE) {
            policy.setEffectiveDate(new Date());
        }
        
        return policyRepository.save(policy);
    }

    @Override
    public BigDecimal calculatePremium(PolicyDTO policyDTO) {
        // 获取产品
        Product product = productRepository.findById(policyDTO.getProductId())
                .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
        
        // 基础保费
        BigDecimal premium = product.getBasePremium();
        
        // 根据保额调整保费
        BigDecimal sumInsuredFactor = policyDTO.getSumInsured()
                .divide(product.getBaseSumInsured(), 4, BigDecimal.ROUND_HALF_UP);
        premium = premium.multiply(sumInsuredFactor);
        
        // 计算附加险保费
        if (policyDTO.getRiders() != null) {
            for (PolicyRiderDTO rider : policyDTO.getRiders()) {
                // 这里应该有更复杂的计算逻辑
                premium = premium.add(rider.getPremium());
            }
        }
        
        return premium.setScale(2, BigDecimal.ROUND_HALF_UP);
    }
    
    private void validateStatusTransition(PolicyStatus currentStatus, PolicyStatus newStatus) {
        // 这里应该有完整的业务规则来验证状态转换是否合法
        // 简化版示例
        if (currentStatus == PolicyStatus.DRAFT && newStatus != PolicyStatus.PENDING) {
            throw new IllegalStateException("Draft can only transition to Pending");
        }
        
        if (currentStatus == PolicyStatus.ACTIVE && 
            (newStatus != PolicyStatus.LAPSED && newStatus != PolicyStatus.CANCELLED)) {
            throw new IllegalStateException("Active policy can only be lapsed or cancelled");
        }
    }
}
java 复制代码
/**
	复杂引擎保费计算
*/
@Service
public class PremiumCalculationEngine{
	
	private final KieContainer kieContainer;
	private final ProductRiskEvaluator riskEvaluator;

	public PremiumCalculationEngine(KieContainer kieContainer, 
                                  ProductRiskEvaluator riskEvaluator) {
        this.kieContainer = kieContainer;
        this.riskEvaluator = riskEvaluator;
    }

	 public BigDecimal calculatePremium(PolicyDTO policyDTO, Product product) {
        KieSession kieSession = kieContainer.newKieSession();
        
        PremiumCalculationContext context = new PremiumCalculationContext();
        context.setBasePremium(product.getBasePremium());
        context.setSumInsured(policyDTO.getSumInsured());
        context.setBaseSumInsured(product.getBaseSumInsured());
        context.setProductType(product.getType());
        context.setRiskLevel(riskEvaluator.evaluateRisk(policyDTO, product));
        
        // 设置附加险信息
        if (policyDTO.getRiders() != null) {
            context.setRiderCount(policyDTO.getRiders().size());
        }
        
        kieSession.insert(context);
        kieSession.fireAllRules();
        kieSession.dispose();
        
        return context.getFinalPremium();
    }
}
java 复制代码
@Data
public class PremiumCalculationContext {
    private BigDecimal basePremium;
    private BigDecimal sumInsured;
    private BigDecimal baseSumInsured;
    private String productType;
    private String riskLevel;
    private int riderCount;
    private BigDecimal finalPremium;
    
    // 其他计算因子...
}
drl 复制代码
package com.insurance.premium.calculation

import com.insurance.service.calculation.PremiumCalculationContext;

rule "Basic premium calculation"
    when
        $context : PremiumCalculationContext()
    then
        // 基础保费计算
        BigDecimal sumInsuredFactor = $context.getSumInsured().divide(
            $context.getBaseSumInsured(), 4, java.math.RoundingMode.HALF_UP);
        $context.setFinalPremium($context.getBasePremium().multiply(sumInsuredFactor));
end

rule "High risk surcharge"
    when
        $context : PremiumCalculationContext(riskLevel == "HIGH")
    then
        // 高风险附加费
        $context.setFinalPremium($context.getFinalPremium().multiply(new BigDecimal("1.2")));
end

rule "Multiple riders discount"
    when
        $context : PremiumCalculationContext(riderCount >= 3)
    then
        // 多个附加险折扣
        $context.setFinalPremium($context.getFinalPremium().multiply(new BigDecimal("0.95")));
end
java 复制代码
/**
	核保流程集成
*/
public interface UnderwritingService {
    UnderwritingStatus performAutoUnderwriting(Policy policy);
    void initiateManualUnderwriting(Long policyId);
    UnderwritingStatus checkUnderwritingStatus(Long policyId);
}
java 复制代码
@Service
public class UnderwritingServiceImpl implements UnderwritingService {
    
    private final PolicyRepository policyRepository;
    private final RabbitTemplate rabbitTemplate;
    private final UnderwritingRuleEngine ruleEngine;
    
    public UnderwritingServiceImpl(PolicyRepository policyRepository,
                                  RabbitTemplate rabbitTemplate,
                                  UnderwritingRuleEngine ruleEngine) {
        this.policyRepository = policyRepository;
        this.rabbitTemplate = rabbitTemplate;
        this.ruleEngine = ruleEngine;
    }
    
    @Override
    @Transactional
    public UnderwritingStatus performAutoUnderwriting(Policy policy) {
        UnderwritingStatus status = ruleEngine.evaluatePolicy(policy);
        
        policy.setUnderwritingStatus(status);
        policyRepository.save(policy);
        
        if (status == UnderwritingStatus.REFERRED) {
            rabbitTemplate.convertAndSend("underwriting.queue", policy.getId());
        }
        
        return status;
    }
    
    @Override
    public void initiateManualUnderwriting(Long policyId) {
        rabbitTemplate.convertAndSend("manual.underwriting.queue", policyId);
    }
    
    @Override
    public UnderwritingStatus checkUnderwritingStatus(Long policyId) {
        return policyRepository.findById(policyId)
                .map(Policy::getUnderwritingStatus)
                .orElse(UnderwritingStatus.PENDING);
    }
}
java 复制代码
/**
	支付集成
*/
public interface PaymentService {
    PaymentResult initiatePayment(PaymentRequest request);
    boolean verifyPayment(String paymentId);
    void processPaymentCallback(String callbackData);
}
java 复制代码
@Service("alipayPaymentService")
@Slf4j
public class AlipayPaymentServiceImpl implements PaymentService {
    
    private final AlipayClient alipayClient;
    
    @Value("${alipay.return-url}")
    private String returnUrl;
    
    @Value("${alipay.notify-url}")
    private String notifyUrl;
    
    public AlipayPaymentServiceImpl(AlipayClient alipayClient) {
        this.alipayClient = alipayClient;
    }
    
    @Override
    public PaymentResult initiatePayment(PaymentRequest request) {
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(returnUrl);
        alipayRequest.setNotifyUrl(notifyUrl);
        
        String subject = "保险产品支付 - 保单号: " + request.getPolicyNo();
        
        alipayRequest.setBizContent("{" +
                "\"out_trade_no\":\"" + request.getOrderId() + "\"," +
                "\"total_amount\":\"" + request.getAmount() + "\"," +
                "\"subject\":\"" + subject + "\"," +
                "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"" +
                "}");
        
        try {
            AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest);
            if (response.isSuccess()) {
                return new PaymentResult(true, response.getBody(), "支付发起成功");
            } else {
                log.error("支付宝支付发起失败: {}", response.getSubMsg());
                return new PaymentResult(false, null, response.getSubMsg());
            }
        } catch (AlipayApiException e) {
            log.error("支付宝支付异常", e);
            return new PaymentResult(false, null, "支付系统异常");
        }
    }
    
    @Override
    public boolean verifyPayment(String paymentId) {
        // 实现支付验证逻辑
        return true;
    }
    
    @Override
    public void processPaymentCallback(String callbackData) {
        // 处理支付回调
    }
}

Repository层

java 复制代码
public interface PolicyRepository extends JpaRepository<Policy, Long> {
    
    Policy findByPolicyNo(String policyNo);
    
    List<Policy> findByCustomerId(Long customerId);
    
    List<Policy> findByStatus(PolicyStatus status);
    
    @Query("SELECT p FROM Policy p WHERE p.effectiveDate <= :date AND p.expiryDate >= :date")
    List<Policy> findActivePolicies(@Param("date") Date date);
    
    @Query("SELECT COUNT(p) FROM Policy p WHERE p.product.id = :productId AND p.status = 'ACTIVE'")
    Long countActivePoliciesByProduct(@Param("productId") Long productId);
}

2. 客户管理

客户信息管理(个人/企业客户)

客户分级与标签

客户360度视图

客户沟通记录

yml 复制代码
# Aliyun OCR配置
aliyun:
  ocr:
    access-key-id: your-access-key-id
    access-key-secret: your-access-key-secret
    endpoint: ocr-api.cn-hangzhou.aliyuncs.com

# Elasticsearch配置
spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200
      username: elastic
      password: yourpassword

# 缓存配置
cache:
  customer:
    ttl: 3600 # 1小时
java 复制代码
@Entity
@Table(name = "customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private CustomerType type; // 个人或企业
    
    @Column(unique = true)
    private String idNumber; // 身份证/统一社会信用代码
    
    @Column(unique = true)
    private String mobile;
    
    @Column(unique = true)
    private String email;
    
    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
    private List<Address> addresses;
    
    @OneToMany(mappedBy = "customer")
    private List<Policy> policies;
    
    @Enumerated(EnumType.STRING)
    private CustomerLevel level; // 客户等级
    
    @Column(nullable = false)
    private Date createdAt;
    
    @Column(nullable = false)
    private Date updatedAt;
    
    private Date birthday;
    private String gender;
    private String occupation;
    
    // 企业客户特有字段
    private String legalRepresentative;
    private String businessScope;
    private Date establishmentDate;
    
    // Getters and Setters
}
java 复制代码
public enum CustomerType {
    INDIVIDUAL,  // 个人客户
    CORPORATE    // 企业客户
}

public enum CustomerLevel {
    REGULAR,     // 普通客户
    SILVER,      // 银牌客户
    GOLD,        // 金牌客户
    PLATINUM,    // 白金客户
    DIAMOND      // 钻石客户
}
java 复制代码
@Entity
@Table(name = "address")
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;
    
    @Column(nullable = false)
    private String province;
    
    @Column(nullable = false)
    private String city;
    
    @Column(nullable = false)
    private String district;
    
    @Column(nullable = false)
    private String detail;
    
    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private AddressType type; // 家庭地址、工作地址等
    
    @Column(nullable = false)
    private Boolean isDefault;
    
    // Getters and Setters
}
java 复制代码
@Data
public class CustomerDTO {
    private Long id;
    
    @NotBlank
    @Size(min = 2, max = 50)
    private String name;
    
    @NotNull
    private CustomerType type;
    
    @NotBlank
    private String idNumber;
    
    @NotBlank
    @Pattern(regexp = "^1[3-9]\\d{9}$")
    private String mobile;
    
    @Email
    private String email;
    
    private CustomerLevel level;
    private Date birthday;
    private String gender;
    private String occupation;
    
    // 企业客户特有字段
    private String legalRepresentative;
    private String businessScope;
    private Date establishmentDate;
    
    @NotNull
    @Size(min = 1)
    private List<AddressDTO> addresses;
    
    @Data
    public static class AddressDTO {
        private Long id;
        
        @NotBlank
        private String province;
        
        @NotBlank
        private String city;
        
        @NotBlank
        private String district;
        
        @NotBlank
        private String detail;
        
        @NotBlank
        private String type;
        
        @NotNull
        private Boolean isDefault;
    }
}
java 复制代码
@Data
public class CustomerSearchDTO {
    private String keyword;
    private CustomerType type;
    private CustomerLevel level;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    
    private String mobile;
    private String idNumber;
}

工具

java 复制代码
public class ExcelHelper {
    
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    
    public static String getCellStringValue(Cell cell) {
        if (cell == null) {
            return "";
        }
        
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date date = cell.getDateCellValue();
                    return dateFormat.format(date);
                } else {
                    return String.valueOf((long) cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                return cell.getCellFormula();
            default:
                return "";
        }
    }
}
java 复制代码
@Component
public class IdCardOCRUtil {
    
    @Value("${aliyun.ocr.access-key-id}")
    private String accessKeyId;
    
    @Value("${aliyun.ocr.access-key-secret}")
    private String accessKeySecret;
    
    @Value("${aliyun.ocr.endpoint}")
    private String endpoint;
    
    public boolean verifyIdCard(String idNumber, String name, 
                              MultipartFile frontImage, MultipartFile backImage) {
        try {
            // 初始化客户端
            com.aliyun.ocr_api20210707.Client client = createClient();
            
            // 验证正面
            RecognizeIdcardRequest frontRequest = new RecognizeIdcardRequest()
                    .setSide("face")
                    .setImageURL(""); // 实际使用中需要上传到OSS获取URL
            
            RecognizeIdcardResponse frontResponse = client.recognizeIdcard(frontRequest);
            String frontIdNumber = frontResponse.getBody().getData().getCardNumber();
            String frontName = frontResponse.getBody().getData().getName();
            
            // 验证背面
            RecognizeIdcardRequest backRequest = new RecognizeIdcardRequest()
                    .setSide("back")
                    .setImageURL(""); // 实际使用中需要上传到OSS获取URL
            
            client.recognizeIdcard(backRequest);
            
            // 验证信息是否匹配
            return idNumber.equals(frontIdNumber) && name.equals(frontName);
        } catch (Exception e) {
            throw new RuntimeException("身份证OCR验证失败", e);
        }
    }
    
    private com.aliyun.ocr_api20210707.Client createClient() throws Exception {
        Config config = new Config()
                .setAccessKeyId(accessKeyId)
                .setAccessKeySecret(accessKeySecret);
        config.endpoint = endpoint;
        return new com.aliyun.ocr_api20210707.Client(config);
    }
}

Controller层

java 复制代码
@RestController
@RequestMapping("/api/customers")
@Api(tags = "客户管理")
public class CustomerController {
    
    private final CustomerService customerService;
    
    public CustomerController(CustomerService customerService) {
        this.customerService = customerService;
    }
    
    @PostMapping
    @ApiOperation("创建客户")
    @PreAuthorize("hasRole('AGENT') or hasRole('ADMIN')")
    public ResponseEntity<Customer> createCustomer(@RequestBody CustomerDTO customerDTO) {
        Customer customer = customerService.createCustomer(customerDTO);
        return ResponseEntity.ok(customer);
    }
    
    @PutMapping("/{id}")
    @ApiOperation("更新客户信息")
    @PreAuthorize("hasRole('AGENT') or hasRole('ADMIN')")
    public ResponseEntity<Customer> updateCustomer(@PathVariable Long id, 
                                                 @RequestBody CustomerDTO customerDTO) {
        Customer customer = customerService.updateCustomer(id, customerDTO);
        return ResponseEntity.ok(customer);
    }
    
    @DeleteMapping("/{id}")
    @ApiOperation("删除客户")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteCustomer(@PathVariable Long id) {
        customerService.deleteCustomer(id);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取客户详情")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Customer> getCustomerById(@PathVariable Long id) {
        Customer customer = customerService.getCustomerById(id);
        return ResponseEntity.ok(customer);
    }
    
    @GetMapping("/id-number/{idNumber}")
    @ApiOperation("通过身份证号获取客户")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Customer> getCustomerByIdNumber(@PathVariable String idNumber) {
        Customer customer = customerService.getCustomerByIdNumber(idNumber);
        return ResponseEntity.ok(customer);
    }
    
    @GetMapping("/search")
    @ApiOperation("搜索客户")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'CLAIM', 'ADMIN')")
    public ResponseEntity<Page<Customer>> searchCustomers(CustomerSearchDTO searchDTO, 
                                                        Pageable pageable) {
        Page<Customer> customers = customerService.searchCustomers(searchDTO, pageable);
        return ResponseEntity.ok(customers);
    }
    
    @GetMapping("/level/{level}")
    @ApiOperation("根据等级获取客户列表")
    @PreAuthorize("hasAnyRole('AGENT', 'ADMIN')")
    public ResponseEntity<List<Customer>> getCustomersByLevel(@PathVariable String level) {
        List<Customer> customers = customerService.getCustomersByLevel(level);
        return ResponseEntity.ok(customers);
    }
    
    @PostMapping("/import")
    @ApiOperation("导入客户Excel")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> importCustomers(@RequestParam("file") MultipartFile file) {
        customerService.importCustomersFromExcel(file);
        return ResponseEntity.ok().build();
    }
    
    @GetMapping("/export")
    @ApiOperation("导出客户Excel")
    @PreAuthorize("hasAnyRole('AGENT', 'ADMIN')")
    public ResponseEntity<byte[]> exportCustomers() {
        byte[] excelData = customerService.exportCustomersToExcel();
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=customers.xlsx")
                .body(excelData);
    }
    
    @PostMapping("/verify-id-card")
    @ApiOperation("身份证验证")
    public ResponseEntity<Customer> verifyIdCard(
            @RequestParam String idNumber,
            @RequestParam String name,
            @RequestParam MultipartFile frontImage,
            @RequestParam MultipartFile backImage) {
        Customer customer = customerService.verifyIdCard(idNumber, name, frontImage, backImage);
        return ResponseEntity.ok(customer);
    }
}

Service层

java 复制代码
@Slf4j
@Service
@Transactional
public class CustomerServiceImpl implements CustomerService {
    
    private final CustomerRepository customerRepository;
    private final AddressRepository addressRepository;
    private final CustomerSearchRepository customerSearchRepository;
    private final ModelMapper modelMapper;
    private final IdCardOCRUtil idCardOCRUtil;
    private final AuditLogService auditLogService;
    
    @Autowired
    public CustomerServiceImpl(CustomerRepository customerRepository,
                             AddressRepository addressRepository,
                             CustomerSearchRepository customerSearchRepository,
                             ModelMapper modelMapper,
                             IdCardOCRUtil idCardOCRUtil,
                             AuditLogService auditLogService) {
        this.customerRepository = customerRepository;
        this.addressRepository = addressRepository;
        this.customerSearchRepository = customerSearchRepository;
        this.modelMapper = modelMapper;
        this.idCardOCRUtil = idCardOCRUtil;
        this.auditLogService = auditLogService;
    }

    @Override
    @CacheEvict(value = "customers", allEntries = true)
    public Customer createCustomer(CustomerDTO customerDTO) {
        // 验证身份证号是否已存在
        if (customerRepository.existsByIdNumber(customerDTO.getIdNumber())) {
            throw new BusinessException("该身份证号已注册");
        }
        
        // 验证手机号是否已存在
        if (customerRepository.existsByMobile(customerDTO.getMobile())) {
            throw new BusinessException("该手机号已注册");
        }
        
        // 创建客户
        Customer customer = modelMapper.map(customerDTO, Customer.class);
        customer.setCreatedAt(new Date());
        customer.setUpdatedAt(new Date());
        
        // 设置默认客户等级
        if (customer.getLevel() == null) {
            customer.setLevel(CustomerLevel.REGULAR);
        }
        
        Customer savedCustomer = customerRepository.save(customer);
        
        // 保存地址
        saveAddresses(customerDTO, savedCustomer);
        
        // 记录审计日志
        auditLogService.logCustomerCreation(savedCustomer);
        
        // 异步同步到Elasticsearch
        syncCustomerToElasticsearchAsync(savedCustomer);
        
        return savedCustomer;
    }

    @Override
    @CacheEvict(value = "customers", key = "#id")
    public Customer updateCustomer(Long id, CustomerDTO customerDTO) {
        Customer existingCustomer = customerRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("客户不存在"));
        
        // 验证身份证号是否已被其他客户使用
        if (!existingCustomer.getIdNumber().equals(customerDTO.getIdNumber()) {
            if (customerRepository.existsByIdNumber(customerDTO.getIdNumber())) {
                throw new BusinessException("该身份证号已被其他客户使用");
            }
        }
        
        // 验证手机号是否已被其他客户使用
        if (!existingCustomer.getMobile().equals(customerDTO.getMobile())) {
            if (customerRepository.existsByMobile(customerDTO.getMobile())) {
                throw new BusinessException("该手机号已被其他客户使用");
            }
        }
        
        modelMapper.map(customerDTO, existingCustomer);
        existingCustomer.setUpdatedAt(new Date());
        
        // 更新地址
        updateAddresses(customerDTO, existingCustomer);
        
        Customer updatedCustomer = customerRepository.save(existingCustomer);
        
        // 异步同步到Elasticsearch
        syncCustomerToElasticsearchAsync(updatedCustomer);
        
        return updatedCustomer;
    }

    @Override
    @CacheEvict(value = "customers", key = "#id")
    public void deleteCustomer(Long id) {
        Customer customer = customerRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("客户不存在"));
        
        // 检查是否有关联保单
        if (!customer.getPolicies().isEmpty()) {
            throw new BusinessException("该客户有关联保单,无法删除");
        }
        
        // 删除地址
        addressRepository.deleteByCustomerId(id);
        
        customerRepository.delete(customer);
        
        // 从Elasticsearch删除
        deleteCustomerFromElasticsearchAsync(id);
    }

    @Override
    @Cacheable(value = "customers", key = "#id")
    public Customer getCustomerById(Long id) {
        return customerRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("客户不存在"));
    }

    @Override
    @Cacheable(value = "customers", key = "#idNumber")
    public Customer getCustomerByIdNumber(String idNumber) {
        return customerRepository.findByIdNumber(idNumber)
                .orElseThrow(() -> new ResourceNotFoundException("客户不存在"));
    }

    @Override
    public Page<Customer> searchCustomers(CustomerSearchDTO searchDTO, Pageable pageable) {
        return customerRepository.searchCustomers(
                searchDTO.getKeyword(),
                searchDTO.getType(),
                searchDTO.getLevel(),
                searchDTO.getStartDate(),
                searchDTO.getEndDate(),
                pageable);
    }

    @Override
    public List<Customer> getCustomersByLevel(String level) {
        CustomerLevel customerLevel = CustomerLevel.valueOf(level.toUpperCase());
        return customerRepository.findByLevel(customerLevel);
    }

    @Override
    public void importCustomersFromExcel(MultipartFile file) {
        try {
            Workbook workbook = new XSSFWorkbook(file.getInputStream());
            Sheet sheet = workbook.getSheetAt(0);
            
            List<Customer> customers = new ArrayList<>();
            
            for (Row row : sheet) {
                if (row.getRowNum() == 0) continue; // 跳过标题行
                
                CustomerDTO dto = new CustomerDTO();
                dto.setName(ExcelHelper.getCellStringValue(row.getCell(0)));
                dto.setType(CustomerType.valueOf(ExcelHelper.getCellStringValue(row.getCell(1))));
                dto.setIdNumber(ExcelHelper.getCellStringValue(row.getCell(2)));
                dto.setMobile(ExcelHelper.getCellStringValue(row.getCell(3)));
                dto.setEmail(ExcelHelper.getCellStringValue(row.getCell(4)));
                
                // 创建客户
                createCustomer(dto);
            }
            
            workbook.close();
        } catch (IOException e) {
            log.error("导入客户Excel失败", e);
            throw new BusinessException("导入客户Excel失败");
        }
    }

    @Override
    public byte[] exportCustomersToExcel() {
        try (Workbook workbook = new XSSFWorkbook(); 
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            
            Sheet sheet = workbook.createSheet("客户列表");
            
            // 创建标题行
            Row headerRow = sheet.createRow(0);
            String[] headers = {"姓名", "类型", "身份证号", "手机号", "邮箱", "等级", "创建时间"};
            for (int i = 0; i < headers.length; i++) {
                headerRow.createCell(i).setCellValue(headers[i]);
            }
            
            // 填充数据
            List<Customer> customers = customerRepository.findAll();
            int rowNum = 1;
            for (Customer customer : customers) {
                Row row = sheet.createRow(rowNum++);
                row.createCell(0).setCellValue(customer.getName());
                row.createCell(1).setCellValue(customer.getType().name());
                row.createCell(2).setCellValue(customer.getIdNumber());
                row.createCell(3).setCellValue(customer.getMobile());
                row.createCell(4).setCellValue(customer.getEmail());
                row.createCell(5).setCellValue(customer.getLevel().name());
                row.createCell(6).setCellValue(customer.getCreatedAt().toString());
            }
            
            workbook.write(out);
            return out.toByteArray();
        } catch (IOException e) {
            log.error("导出客户Excel失败", e);
            throw new BusinessException("导出客户Excel失败");
        }
    }

    @Override
    @Async
    public void syncCustomersToElasticsearch() {
        Iterable<Customer> customers = customerRepository.findAll();
        customerSearchRepository.saveAll(customers);
        log.info("同步所有客户数据到Elasticsearch完成");
    }

    @Override
    public Customer verifyIdCard(String idNumber, String name, MultipartFile frontImage, MultipartFile backImage) {
        // 使用OCR验证身份证
        boolean verified = idCardOCRUtil.verifyIdCard(idNumber, name, frontImage, backImage);
        
        if (!verified) {
            throw new BusinessException("身份证验证失败");
        }
        
        // 查找或创建客户
        Customer customer = customerRepository.findByIdNumber(idNumber)
                .orElseGet(() -> {
                    CustomerDTO dto = new CustomerDTO();
                    dto.setName(name);
                    dto.setType(CustomerType.INDIVIDUAL);
                    dto.setIdNumber(idNumber);
                    dto.setMobile(""); // 需要后续补充
                    return createCustomer(dto);
                });
        
        // 标记为已验证
        customer.setIdVerified(true);
        customerRepository.save(customer);
        
        return customer;
    }
    
    private void saveAddresses(CustomerDTO customerDTO, Customer customer) {
        for (CustomerDTO.AddressDTO addressDTO : customerDTO.getAddresses()) {
            Address address = modelMapper.map(addressDTO, Address.class);
            address.setCustomer(customer);
            addressRepository.save(address);
        }
    }
    
    private void updateAddresses(CustomerDTO customerDTO, Customer customer) {
        // 删除旧地址
        addressRepository.deleteByCustomerId(customer.getId());
        
        // 添加新地址
        saveAddresses(customerDTO, customer);
    }
    
    @Async
    protected void syncCustomerToElasticsearchAsync(Customer customer) {
        try {
            customerSearchRepository.save(customer);
            log.info("同步客户数据到Elasticsearch: {}", customer.getId());
        } catch (Exception e) {
            log.error("同步客户到Elasticsearch失败: {}", customer.getId(), e);
        }
    }
    
    @Async
    protected void deleteCustomerFromElasticsearchAsync(Long customerId) {
        try {
            customerSearchRepository.deleteById(customerId);
            log.info("从Elasticsearch删除客户: {}", customerId);
        } catch (Exception e) {
            log.error("从Elasticsearch删除客户失败: {}", customerId, e);
        }
    }
}

Repository层

java 复制代码
public interface CustomerRepository extends JpaRepository<Customer, Long> {
    
    Optional<Customer> findByIdNumber(String idNumber);
    
    Optional<Customer> findByMobile(String mobile);
    
    List<Customer> findByType(CustomerType type);
    
    List<Customer> findByLevel(CustomerLevel level);
    
    @Query("SELECT c FROM Customer c WHERE " +
           "(:keyword IS NULL OR c.name LIKE %:keyword% OR c.mobile LIKE %:keyword%) AND " +
           "(:type IS NULL OR c.type = :type) AND " +
           "(:level IS NULL OR c.level = :level) AND " +
           "(:startDate IS NULL OR c.createdAt >= :startDate) AND " +
           "(:endDate IS NULL OR c.createdAt <= :endDate)")
    Page<Customer> searchCustomers(@Param("keyword") String keyword,
                                 @Param("type") CustomerType type,
                                 @Param("level") CustomerLevel level,
                                 @Param("startDate") Date startDate,
                                 @Param("endDate") Date endDate,
                                 Pageable pageable);
    
    @Query("SELECT COUNT(c) FROM Customer c WHERE c.createdAt BETWEEN :start AND :end")
    Long countByCreateDateBetween(@Param("start") Date start, @Param("end") Date end);
}
java 复制代码
@Repository
public interface CustomerSearchRepository extends ElasticsearchRepository<Customer, Long> {
    
    List<Customer> findByNameOrMobileOrIdNumber(String name, String mobile, String idNumber);
    
    List<Customer> findByLevel(String level);
}

3. 产品管理

保险产品配置(条款、费率、规则)

产品生命周期管理

产品组合与打包

java 复制代码
@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String code; // 产品代码
    
    @Column(nullable = false)
    private String name; // 产品名称
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private ProductType type; // 产品类型
    
    @Column(nullable = false)
    private String description;
    
    @Column(nullable = false)
    private BigDecimal basePremium; // 基础保费
    
    @Column(nullable = false)
    private BigDecimal baseSumInsured; // 基础保额
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private ProductStatus status; // 产品状态
    
    @Column(nullable = false)
    private Date effectiveDate; // 生效日期
    
    @Column(nullable = false)
    private Date expiryDate; // 失效日期
    
    @Column(nullable = false)
    private Integer minAge; // 最小投保年龄
    
    @Column(nullable = false)
    private Integer maxAge; // 最大投保年龄
    
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    private List<ProductRider> riders; // 可选附加险
    
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    private List<ProductClause> clauses; // 条款
    
    @OneToMany(mappedBy = "product")
    private List<Policy> policies; // 关联保单
    
    @Column(nullable = false)
    private Date createdAt;
    
    @Column(nullable = false)
    private Date updatedAt;
    
    @Version
    private Long version; // 乐观锁版本号
    
    // Getters and Setters
}
java 复制代码
public enum ProductType {
    LIFE,           // 寿险
    HEALTH,         // 健康险
    ACCIDENT,       // 意外险
    PROPERTY,       // 财产险
    LIABILITY,      // 责任险
    TRAVEL,         // 旅行险
    ANNUITY         // 年金险
}
java 复制代码
public enum ProductStatus {
    DRAFT,          // 草稿
    PENDING,        // 待审核
    APPROVED,       // 已审核
    ACTIVE,         // 已生效
    INACTIVE,       // 已停售
    ARCHIVED        // 已归档
}
java 复制代码
@Entity
@Table(name = "product_rider")
public class ProductRider {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;
    
    @Column(nullable = false)
    private String code;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String description;
    
    @Column(nullable = false)
    private BigDecimal premium; // 附加险保费
    
    @Column(nullable = false)
    private BigDecimal sumInsured; // 附加险保额
    
    @Column(nullable = false)
    private Boolean isDefault; // 是否默认选择
    
    // Getters and Setters
}
java 复制代码
@Entity
@Table(name = "product_clause")
public class ProductClause {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;
    
    @Column(nullable = false)
    private String clauseNo; // 条款编号
    
    @Column(nullable = false)
    private String title; // 条款标题
    
    @Lob
    @Column(nullable = false)
    private String content; // 条款内容
    
    @Column(nullable = false)
    private Integer displayOrder; // 显示顺序
    
    // Getters and Setters
}
java 复制代码
@Data
public class ProductDTO {
    private Long id;
    
    @NotBlank
    @Size(min = 2, max = 20)
    private String code;
    
    @NotBlank
    @Size(min = 2, max = 100)
    private String name;
    
    @NotNull
    private ProductType type;
    
    @NotBlank
    @Size(min = 10, max = 1000)
    private String description;
    
    @NotNull
    @DecimalMin("0.00")
    private BigDecimal basePremium;
    
    @NotNull
    @DecimalMin("0.00")
    private BigDecimal baseSumInsured;
    
    private ProductStatus status;
    
    @NotNull
    @Future
    private Date effectiveDate;
    
    @NotNull
    @Future
    private Date expiryDate;
    
    @NotNull
    @Min(0)
    @Max(120)
    private Integer minAge;
    
    @NotNull
    @Min(0)
    @Max(120)
    private Integer maxAge;
    
    @NotNull
    @Size(min = 1)
    private List<ProductRiderDTO> riders;
    
    @NotNull
    @Size(min = 1)
    private List<ProductClauseDTO> clauses;
    
    @Data
    public static class ProductRiderDTO {
        private Long id;
        
        @NotBlank
        private String code;
        
        @NotBlank
        private String name;
        
        @NotBlank
        private String description;
        
        @NotNull
        @DecimalMin("0.00")
        private BigDecimal premium;
        
        @NotNull
        @DecimalMin("0.00")
        private BigDecimal sumInsured;
        
        @NotNull
        private Boolean isDefault;
    }
    
    @Data
    public static class ProductClauseDTO {
        private Long id;
        
        @NotBlank
        private String clauseNo;
        
        @NotBlank
        private String title;
        
        @NotBlank
        private String content;
        
        @NotNull
        @Min(1)
        private Integer displayOrder;
    }
}
java 复制代码
@Data
public class ProductSearchDTO {
    private String keyword;
    private ProductType type;
    private ProductStatus status;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date effectiveDateFrom;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date effectiveDateTo;
    
    private Integer minAge;
    private Integer maxAge;
}
java 复制代码
/**
	DroolsRuleGenerator工具
*/
@Component
public class DroolsRuleGenerator {
    
    public String generateProductRules(Product product) {
        StringBuilder rules = new StringBuilder();
        
        // 包声明
        rules.append(MessageFormat.format(
                "package com.insurance.rules.product.{0};\n\n", product.getCode().toLowerCase()));
        
        // 导入
        rules.append("import com.insurance.dto.PolicyDTO;\n");
        rules.append("import com.insurance.service.calculation.PremiumCalculationContext;\n");
        rules.append("import java.math.BigDecimal;\n\n");
        
        // 规则1: 基础保费计算
        rules.append(MessageFormat.format(
                "rule \"Base premium calculation - {0}\"\n", product.getCode()));
        rules.append("when\n");
        rules.append("    $context : PremiumCalculationContext( productCode == \"" + product.getCode() + "\" )\n");
        rules.append("then\n");
        rules.append(MessageFormat.format(
                "    BigDecimal sumInsuredFactor = $context.getSumInsured().divide(new BigDecimal(\"{0}\"), 4, BigDecimal.ROUND_HALF_UP);\n", 
                product.getBaseSumInsured()));
        rules.append(MessageFormat.format(
                "    $context.setFinalPremium(new BigDecimal(\"{0}\").multiply(sumInsuredFactor));\n", 
                product.getBasePremium()));
        rules.append("end\n\n");
        
        // 规则2: 年龄限制
        rules.append(MessageFormat.format(
                "rule \"Age restriction - {0}\"\n", product.getCode()));
        rules.append("when\n");
        rules.append("    $context : PremiumCalculationContext( productCode == \"" + product.getCode() + "\" )\n");
        rules.append(MessageFormat.format(
                "    PolicyDTO( applicantAge < {0} || applicantAge > {1} )\n", 
                product.getMinAge(), product.getMaxAge()));
        rules.append("then\n");
        rules.append("    throw new RuntimeException(\"投保年龄不符合产品要求\");\n");
        rules.append("end\n\n");
        
        // 为每个附加险生成规则
        for (ProductRider rider : product.getRiders()) {
            rules.append(MessageFormat.format(
                    "rule \"Rider premium - {0} - {1}\"\n", product.getCode(), rider.getCode()));
            rules.append("when\n");
            rules.append("    $context : PremiumCalculationContext( productCode == \"" + product.getCode() + "\" )\n");
            rules.append(MessageFormat.format(
                    "    PolicyDTO( riders contains \"{0}\" )\n", rider.getCode()));
            rules.append("then\n");
            rules.append(MessageFormat.format(
                    "    $context.setFinalPremium($context.getFinalPremium().add(new BigDecimal(\"{0}\")));\n", 
                    rider.getPremium()));
            rules.append("end\n\n");
        }
        
        return rules.toString();
    }
}

Controller层

java 复制代码
@RestController
@RequestMapping("/api/products")
@Api(tags = "产品管理")
public class ProductController {
    
    private final ProductService productService;
    
    public ProductController(ProductService productService) {
        this.productService = productService;
    }
    
    @PostMapping
    @ApiOperation("创建产品")
    @PreAuthorize("hasRole('PRODUCT_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<Product> createProduct(@RequestBody ProductDTO productDTO) {
        Product product = productService.createProduct(productDTO);
        return ResponseEntity.ok(product);
    }
    
    @PutMapping("/{id}")
    @ApiOperation("更新产品")
    @PreAuthorize("hasRole('PRODUCT_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, 
                                               @RequestBody ProductDTO productDTO) {
        Product product = productService.updateProduct(id, productDTO);
        return ResponseEntity.ok(product);
    }
    
    @DeleteMapping("/{id}")
    @ApiOperation("删除产品")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取产品详情")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'PRODUCT_MANAGER', 'ADMIN')")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Product product = productService.getProductById(id);
        return ResponseEntity.ok(product);
    }
    
    @GetMapping("/code/{code}")
    @ApiOperation("通过产品代码获取产品")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'PRODUCT_MANAGER', 'ADMIN')")
    public ResponseEntity<Product> getProductByCode(@PathVariable String code) {
        Product product = productService.getProductByCode(code);
        return ResponseEntity.ok(product);
    }
    
    @GetMapping("/search")
    @ApiOperation("搜索产品")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'PRODUCT_MANAGER', 'ADMIN')")
    public ResponseEntity<Page<Product>> searchProducts(ProductSearchDTO searchDTO, 
                                                      Pageable pageable) {
        Page<Product> products = productService.searchProducts(searchDTO, pageable);
        return ResponseEntity.ok(products);
    }
    
    @GetMapping("/active")
    @ApiOperation("获取有效产品列表")
    @PreAuthorize("hasAnyRole('AGENT', 'UNDERWRITER', 'PRODUCT_MANAGER', 'ADMIN')")
    public ResponseEntity<List<Product>> getActiveProducts() {
        List<Product> products = productService.getActiveProducts();
        return ResponseEntity.ok(products);
    }
    
    @PutMapping("/{id}/status/{status}")
    @ApiOperation("变更产品状态")
    @PreAuthorize("hasRole('PRODUCT_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<Product> changeProductStatus(@PathVariable Long id, 
                                                     @PathVariable String status) {
        Product product = productService.changeProductStatus(id, status);
        return ResponseEntity.ok(product);
    }
    
    @GetMapping("/expiring")
    @ApiOperation("获取即将过期的产品")
    @PreAuthorize("hasRole('PRODUCT_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<List<Product>> findProductsToExpire() {
        List<Product> products = productService.findProductsToExpire();
        return ResponseEntity.ok(products);
    }
    
    @PostMapping("/expire")
    @ApiOperation("批量停用产品")
    @PreAuthorize("hasRole('PRODUCT_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<Void> expireProducts(@RequestBody List<Long> productIds) {
        productService.expireProducts(productIds);
        return ResponseEntity.noContent().build();
    }
    
    @PostMapping("/sync-to-es")
    @ApiOperation("同步产品到Elasticsearch")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> syncProductsToElasticsearch() {
        productService.syncProductsToElasticsearch();
        return ResponseEntity.noContent().build();
    }
}

Service层

java 复制代码
public interface ProductService {
    Product createProduct(ProductDTO productDTO);
    Product updateProduct(Long id, ProductDTO productDTO);
    void deleteProduct(Long id);
    Product getProductById(Long id);
    Product getProductByCode(String code);
    Page<Product> searchProducts(ProductSearchDTO searchDTO, Pageable pageable);
    List<Product> getActiveProducts();
    Product changeProductStatus(Long id, String status);
    List<Product> findProductsToExpire();
    void expireProducts(List<Long> productIds);
    void syncProductsToElasticsearch();
}
java 复制代码
@Slf4j
@Service
@Transactional
public class ProductServiceImpl implements ProductService {
    
    private final ProductRepository productRepository;
    private final ProductRiderRepository riderRepository;
    private final ProductClauseRepository clauseRepository;
    private final ProductSearchRepository productSearchRepository;
    private final ModelMapper modelMapper;
    private final DroolsRuleGenerator droolsRuleGenerator;
    private final AuditLogService auditLogService;
    
    public ProductServiceImpl(ProductRepository productRepository,
                            ProductRiderRepository riderRepository,
                            ProductClauseRepository clauseRepository,
                            ProductSearchRepository productSearchRepository,
                            ModelMapper modelMapper,
                            DroolsRuleGenerator droolsRuleGenerator,
                            AuditLogService auditLogService) {
        this.productRepository = productRepository;
        this.riderRepository = riderRepository;
        this.clauseRepository = clauseRepository;
        this.productSearchRepository = productSearchRepository;
        this.modelMapper = modelMapper;
        this.droolsRuleGenerator = droolsRuleGenerator;
        this.auditLogService = auditLogService;
    }

    @Override
    @CacheEvict(value = "products", allEntries = true)
    public Product createProduct(ProductDTO productDTO) {
        // 验证产品代码是否已存在
        if (productRepository.existsByCode(productDTO.getCode())) {
            throw new BusinessException("产品代码已存在");
        }
        
        // 创建产品
        Product product = modelMapper.map(productDTO, Product.class);
        product.setStatus(ProductStatus.DRAFT);
        product.setCreatedAt(new Date());
        product.setUpdatedAt(new Date());
        
        Product savedProduct = productRepository.save(product);
        
        // 保存附加险
        saveRiders(productDTO, savedProduct);
        
        // 保存条款
        saveClauses(productDTO, savedProduct);
        
        // 生成Drools规则
        generateDroolsRules(savedProduct);
        
        // 记录审计日志
        auditLogService.logProductCreation(savedProduct);
        
        // 异步同步到Elasticsearch
        syncProductToElasticsearchAsync(savedProduct);
        
        return savedProduct;
    }

    @Override
    @CacheEvict(value = "products", key = "#id")
    public Product updateProduct(Long id, ProductDTO productDTO) {
        Product existingProduct = productRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
        
        // 验证产品代码是否已被其他产品使用
        if (!existingProduct.getCode().equals(productDTO.getCode())) {
            if (productRepository.existsByCode(productDTO.getCode())) {
                throw new BusinessException("产品代码已被其他产品使用");
            }
        }
        
        // 检查是否可以修改
        if (existingProduct.getStatus() != ProductStatus.DRAFT && 
            existingProduct.getStatus() != ProductStatus.PENDING) {
            throw new BusinessException("只有草稿或待审核状态的产品可以修改");
        }
        
        modelMapper.map(productDTO, existingProduct);
        existingProduct.setUpdatedAt(new Date());
        
        // 更新附加险
        updateRiders(productDTO, existingProduct);
        
        // 更新条款
        updateClauses(productDTO, existingProduct);
        
        Product updatedProduct = productRepository.save(existingProduct);
        
        // 重新生成Drools规则
        generateDroolsRules(updatedProduct);
        
        // 异步同步到Elasticsearch
        syncProductToElasticsearchAsync(updatedProduct);
        
        return updatedProduct;
    }

    @Override
    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
        
        // 检查是否有关联保单
        if (!product.getPolicies().isEmpty()) {
            throw new BusinessException("该产品有关联保单,无法删除");
        }
        
        // 删除附加险
        riderRepository.deleteByProductId(id);
        
        // 删除条款
        clauseRepository.deleteByProductId(id);
        
        productRepository.delete(product);
        
        // 从Elasticsearch删除
        deleteProductFromElasticsearchAsync(id);
    }

    @Override
    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        return productRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
    }

    @Override
    @Cacheable(value = "products", key = "#code")
    public Product getProductByCode(String code) {
        return productRepository.findByCode(code)
                .orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
    }

    @Override
    public Page<Product> searchProducts(ProductSearchDTO searchDTO, Pageable pageable) {
        return productRepository.searchProducts(
                searchDTO.getKeyword(),
                searchDTO.getType(),
                searchDTO.getStatus(),
                searchDTO.getEffectiveDateFrom(),
                searchDTO.getEffectiveDateTo(),
                searchDTO.getMinAge(),
                searchDTO.getMaxAge(),
                pageable);
    }

    @Override
    public List<Product> getActiveProducts() {
        return productRepository.findActiveProducts(new Date());
    }

    @Override
    @CacheEvict(value = "products", key = "#id")
    public Product changeProductStatus(Long id, String status) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
        
        ProductStatus newStatus = ProductStatus.valueOf(status.toUpperCase());
        
        // 验证状态转换是否合法
        validateStatusTransition(product.getStatus(), newStatus);
        
        product.setStatus(newStatus);
        product.setUpdatedAt(new Date());
        
        Product updatedProduct = productRepository.save(product);
        
        // 异步同步到Elasticsearch
        syncProductToElasticsearchAsync(updatedProduct);
        
        return updatedProduct;
    }

    @Override
    public List<Product> findProductsToExpire() {
        return productRepository.findProductsToExpire(new Date());
    }

    @Override
    @CacheEvict(value = "products", allEntries = true)
    public void expireProducts(List<Long> productIds) {
        List<Product> products = productRepository.findAllById(productIds);
        
        products.forEach(product -> {
            product.setStatus(ProductStatus.INACTIVE);
            product.setUpdatedAt(new Date());
            productRepository.save(product);
            
            // 异步同步到Elasticsearch
            syncProductToElasticsearchAsync(product);
        });
    }

    @Override
    @Async
    public void syncProductsToElasticsearch() {
        Iterable<Product> products = productRepository.findAll();
        productSearchRepository.saveAll(products);
        log.info("同步所有产品数据到Elasticsearch完成");
    }
    
    @Scheduled(cron = "0 0 0 * * ?") // 每天午夜执行
    public void autoUpdateProductStatus() {
        Date today = new Date();
        
        // 激活到期的产品
        List<Product> productsToActivate = productRepository
                .findByStatusAndEffectiveDateLessThanEqual(ProductStatus.APPROVED, today);
        
        productsToActivate.forEach(product -> {
            product.setStatus(ProductStatus.ACTIVE);
            productRepository.save(product);
            syncProductToElasticsearchAsync(product);
        });
        
        // 停用过期的产品
        List<Product> productsToExpire = productRepository.findProductsToExpire(today);
        
        productsToExpire.forEach(product -> {
            product.setStatus(ProductStatus.INACTIVE);
            productRepository.save(product);
            syncProductToElasticsearchAsync(product);
        });
    }
    
    private void saveRiders(ProductDTO productDTO, Product product) {
        for (ProductDTO.ProductRiderDTO riderDTO : productDTO.getRiders()) {
            ProductRider rider = modelMapper.map(riderDTO, ProductRider.class);
            rider.setProduct(product);
            riderRepository.save(rider);
        }
    }
    
    private void updateRiders(ProductDTO productDTO, Product product) {
        // 删除旧附加险
        riderRepository.deleteByProductId(product.getId());
        
        // 添加新附加险
        saveRiders(productDTO, product);
    }
    
    private void saveClauses(ProductDTO productDTO, Product product) {
        for (ProductDTO.ProductClauseDTO clauseDTO : productDTO.getClauses()) {
            ProductClause clause = modelMapper.map(clauseDTO, ProductClause.class);
            clause.setProduct(product);
            clauseRepository.save(clause);
        }
    }
    
    private void updateClauses(ProductDTO productDTO, Product product) {
        // 删除旧条款
        clauseRepository.deleteByProductId(product.getId());
        
        // 添加新条款
        saveClauses(productDTO, product);
    }
    
    private void generateDroolsRules(Product product) {
        String ruleContent = droolsRuleGenerator.generateProductRules(product);
        // 保存规则到文件或数据库,供Drools引擎加载
        log.info("Generated Drools rules for product {}:\n{}", product.getCode(), ruleContent);
    }
    
    private void validateStatusTransition(ProductStatus currentStatus, ProductStatus newStatus) {
        // 这里应该有完整的业务规则来验证状态转换是否合法
        // 简化版示例
        if (currentStatus == ProductStatus.DRAFT && 
            !(newStatus == ProductStatus.PENDING || newStatus == ProductStatus.DRAFT)) {
            throw new BusinessException("草稿状态只能转为待审核状态");
        }
        
        if (currentStatus == ProductStatus.ACTIVE && newStatus != ProductStatus.INACTIVE) {
            throw new BusinessException("生效产品只能停售");
        }
    }
    
    @Async
    protected void syncProductToElasticsearchAsync(Product product) {
        try {
            productSearchRepository.save(product);
            log.info("同步产品数据到Elasticsearch: {}", product.getId());
        } catch (Exception e) {
            log.error("同步产品到Elasticsearch失败: {}", product.getId(), e);
        }
    }
    
    @Async
    protected void deleteProductFromElasticsearchAsync(Long productId) {
        try {
            productSearchRepository.deleteById(productId);
            log.info("从Elasticsearch删除产品: {}", productId);
        } catch (Exception e) {
            log.error("从Elasticsearch删除产品失败: {}", productId, e);
        }
    }
}

Repository层

java 复制代码
public interface ProductRepository extends JpaRepository<Product, Long> {
    
    Optional<Product> findByCode(String code);
    
    List<Product> findByType(ProductType type);
    
    List<Product> findByStatus(ProductStatus status);
    
    @Query("SELECT p FROM Product p WHERE " +
           "(:keyword IS NULL OR p.name LIKE %:keyword% OR p.code LIKE %:keyword%) AND " +
           "(:type IS NULL OR p.type = :type) AND " +
           "(:status IS NULL OR p.status = :status) AND " +
           "(:effectiveDateFrom IS NULL OR p.effectiveDate >= :effectiveDateFrom) AND " +
           "(:effectiveDateTo IS NULL OR p.effectiveDate <= :effectiveDateTo) AND " +
           "(:minAge IS NULL OR p.minAge <= :minAge) AND " +
           "(:maxAge IS NULL OR p.maxAge >= :maxAge)")
    Page<Product> searchProducts(@Param("keyword") String keyword,
                               @Param("type") ProductType type,
                               @Param("status") ProductStatus status,
                               @Param("effectiveDateFrom") Date effectiveDateFrom,
                               @Param("effectiveDateTo") Date effectiveDateTo,
                               @Param("minAge") Integer minAge,
                               @Param("maxAge") Integer maxAge,
                               Pageable pageable);
    
    @Query("SELECT p FROM Product p WHERE p.effectiveDate <= :date AND p.expiryDate >= :date AND p.status = 'ACTIVE'")
    List<Product> findActiveProducts(@Param("date") Date date);
    
    @Query("SELECT p FROM Product p WHERE p.expiryDate < :date AND p.status = 'ACTIVE'")
    List<Product> findProductsToExpire(@Param("date") Date date);
}
java 复制代码
@Repository
public interface ProductSearchRepository extends ElasticsearchRepository<Product, Long> {
    
    List<Product> findByNameOrCode(String name, String code);
    
    List<Product> findByType(String type);
    
    List<Product> findByStatus(String status);
}

4. 核保管理

自动核保规则引擎

人工核保工作流

风险评估模型

再保管理

yml 复制代码
# Activiti配置
activiti:
  check-process-definitions: true
  database-schema-update: true
  history-level: full
  async-executor-activate: true

# Drools配置
drools:
  kie-container-id: underwritingKieContainer

# 缓存配置
cache:
  underwriting:
    ttl: 3600 # 1小时

工作流配置Activiti

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:activiti="http://activiti.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.activiti.org/processdef">
    
    <process id="underwritingProcess" name="Underwriting Process" isExecutable="true">
        
        <startEvent id="startEvent" name="Start"/>
        
        <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="autoUnderwritingTask"/>
        
        <serviceTask id="autoUnderwritingTask" name="Auto Underwriting"
                    activiti:class="com.insurance.workflow.AutoUnderwritingDelegate"/>
        
        <sequenceFlow id="flow2" sourceRef="autoUnderwritingTask" targetRef="decisionGateway"/>
        
        <exclusiveGateway id="decisionGateway" name="Underwriting Decision"/>
        
        <sequenceFlow id="flowApproved" sourceRef="decisionGateway" targetRef="approvedEndEvent">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${decision == 'APPROVED'}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <sequenceFlow id="flowDeclined" sourceRef="decisionGateway" targetRef="declinedEndEvent">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${decision == 'DECLINED'}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <sequenceFlow id="flowReferred" sourceRef="decisionGateway" targetRef="manualUnderwritingTask">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${decision == 'REFERRED'}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <userTask id="manualUnderwritingTask" name="Manual Underwriting"
                 activiti:candidateGroups="underwriters">
            <documentation>Manual underwriting task</documentation>
        </userTask>
        
        <sequenceFlow id="flow3" sourceRef="manualUnderwritingTask" targetRef="decisionGateway2"/>
        
        <exclusiveGateway id="decisionGateway2" name="Manual Underwriting Decision"/>
        
        <sequenceFlow id="flow4" sourceRef="decisionGateway2" targetRef="approvedEndEvent">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${decision == 'APPROVED'}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <sequenceFlow id="flow5" sourceRef="decisionGateway2" targetRef="declinedEndEvent">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${decision == 'DECLINED'}]]>
            </conditionExpression>
        </sequenceFlow>
        
        <endEvent id="approvedEndEvent" name="Approved">
            <terminateEventDefinition/>
        </endEvent>
        
        <endEvent id="declinedEndEvent" name="Declined">
            <terminateEventDefinition/>
        </endEvent>
    </process>
</definitions>
java 复制代码
@Component
public class AutoUnderwritingDelegate implements JavaDelegate {
    
    private final UnderwritingService underwritingService;
    
    @Autowired
    public AutoUnderwritingDelegate(UnderwritingService underwritingService) {
        this.underwritingService = underwritingService;
    }
    
    @Override
    public void execute(DelegateExecution execution) {
        Long policyId = (Long) execution.getVariable("policyId");
        underwritingService.performAutoUnderwriting(policyId);
        
        // 设置决策变量
        execution.setVariable("decision", execution.getVariable("decision"));
        execution.setVariable("decisionReason", execution.getVariable("decisionReason"));
    }
}

规则引擎

java 复制代码
@Component
public class DroolsUnderwritingEngine {
    
    private final KieContainer kieContainer;
    
    public DroolsUnderwritingEngine(KieContainer kieContainer) {
        this.kieContainer = kieContainer;
    }
    
    public UnderwritingDecision evaluate(Policy policy, List<UnderwritingRule> rules) {
        KieSession kieSession = kieContainer.newKieSession();
        
        // 设置决策上下文
        UnderwritingDecision decision = new UnderwritingDecision();
        decision.setStatus(UnderwritingStatus.PENDING);
        
        // 插入事实
        kieSession.insert(policy);
        kieSession.insert(decision);
        rules.forEach(kieSession::insert);
        
        // 执行规则
        kieSession.fireAllRules();
        kieSession.dispose();
        
        return decision;
    }
    
    public static class UnderwritingDecision {
        private UnderwritingStatus status;
        private String reason;
        
        // Getters and Setters
    }
}
java 复制代码
@Data
public class UnderwritingRuleDTO {
    private Long id;
    
    @NotNull
    private Long productId;
    
    @NotBlank
    private String ruleName;
    
    @NotBlank
    private String ruleExpression;
    
    @NotBlank
    private String decision;
    
    @NotNull
    private Integer priority;
    
    @NotNull
    private Boolean isActive;
}
java 复制代码
@Data
public class UnderwritingDecisionDTO {
    @NotNull
    private Long underwritingId;
    
    @NotNull
    private UnderwritingStatus decision;
    
    @NotBlank
    private String reason;
    
    private String notes;
}
java 复制代码
@Data
public class UnderwritingDTO {
    private Long id;
    
    @NotNull
    private Long policyId;
    
    @NotNull
    private UnderwritingStatus status;
    
    private UnderwritingType type;
    
    private String decisionReason;
    private String assignedTo;
    private String notes;
    
    @NotNull
    private Date createdAt;
    
    @NotNull
    private Date updatedAt;
    
    private Date completedAt;
}
java 复制代码
@Entity
@Table(name = "underwriting_rule")
public class UnderwritingRule {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product; // 关联产品
    
    @Column(nullable = false)
    private String ruleName; // 规则名称
    
    @Column(nullable = false)
    private String ruleExpression; // 规则表达式
    
    @Column(nullable = false)
    private String decision; // 决策结果(通过/拒绝/转人工)
    
    @Column(nullable = false)
    private Integer priority; // 优先级
    
    @Column(nullable = false)
    private Boolean isActive; // 是否激活
    
    // Getters and Setters
}
java 复制代码
public enum UnderwritingType {
    AUTO,           // 自动核保
    MANUAL          // 人工核保
}

public enum UnderwritingStatus {
    PENDING,        // 待核保
    IN_PROGRESS,    // 核保中
    APPROVED,       // 已通过
    DECLINED,       // 已拒绝
    REFERRED,       // 需进一步核保
    CANCELLED       // 已取消
}
java 复制代码
@Entity
@Table(name = "underwriting")
public class Underwriting {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToOne
    @JoinColumn(name = "policy_id", nullable = false)
    private Policy policy; // 关联保单
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private UnderwritingStatus status; // 核保状态
    
    @Enumerated(EnumType.STRING)
    private UnderwritingType type; // 核保类型(自动/人工)
    
    @Column(length = 2000)
    private String decisionReason; // 核保结论原因
    
    @Column
    private String assignedTo; // 分配给哪个核保员
    
    @Column(nullable = false)
    private Date createdAt;
    
    @Column(nullable = false)
    private Date updatedAt;
    
    @Column
    private Date completedAt; // 完成时间
    
    @Column(length = 2000)
    private String notes; // 核保备注
    
    // Getters and Setters
}

Controller层

java 复制代码
@RestController
@RequestMapping("/api/underwritings")
@Api(tags = "核保管理")
public class UnderwritingController {
    
    private final UnderwritingService underwritingService;
    
    public UnderwritingController(UnderwritingService underwritingService) {
        this.underwritingService = underwritingService;
    }
    
    @PostMapping("/initiate/{policyId}")
    @ApiOperation("发起核保")
    @PreAuthorize("hasRole('UNDERWRITER') or hasRole('ADMIN')")
    public ResponseEntity<Underwriting> initiateUnderwriting(@PathVariable Long policyId) {
        Underwriting underwriting = underwritingService.initiateUnderwriting(policyId);
        return ResponseEntity.ok(underwriting);
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取核保详情")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'ADMIN')")
    public ResponseEntity<Underwriting> getUnderwritingById(@PathVariable Long id) {
        Underwriting underwriting = underwritingService.getUnderwritingById(id);
        return ResponseEntity.ok(underwriting);
    }
    
    @GetMapping("/policy/{policyId}")
    @ApiOperation("通过保单ID获取核保记录")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'ADMIN')")
    public ResponseEntity<Underwriting> getUnderwritingByPolicyId(@PathVariable Long policyId) {
        Underwriting underwriting = underwritingService.getUnderwritingByPolicyId(policyId);
        return ResponseEntity.ok(underwriting);
    }
    
    @GetMapping("/search")
    @ApiOperation("搜索核保记录")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'ADMIN')")
    public ResponseEntity<Page<Underwriting>> searchUnderwritings(UnderwritingSearchDTO searchDTO, 
                                                                 Pageable pageable) {
        Page<Underwriting> underwritings = underwritingService.searchUnderwritings(searchDTO, pageable);
        return ResponseEntity.ok(underwritings);
    }
    
    @PostMapping("/decision")
    @ApiOperation("核保决策")
    @PreAuthorize("hasRole('UNDERWRITER') or hasRole('ADMIN')")
    public ResponseEntity<Underwriting> makeDecision(@RequestBody UnderwritingDecisionDTO decisionDTO) {
        Underwriting underwriting = underwritingService.makeDecision(decisionDTO);
        return ResponseEntity.ok(underwriting);
    }
    
    @PutMapping("/{id}/assign/{assignee}")
    @ApiOperation("分配核保任务")
    @PreAuthorize("hasRole('UNDERWRITER_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<Underwriting> assignUnderwriting(@PathVariable Long id, 
                                                         @PathVariable String assignee) {
        Underwriting underwriting = underwritingService.assignUnderwriting(id, assignee);
        return ResponseEntity.ok(underwriting);
    }
    
    @PostMapping("/rules")
    @ApiOperation("创建核保规则")
    @PreAuthorize("hasRole('UNDERWRITER_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<UnderwritingRule> createUnderwritingRule(@RequestBody UnderwritingRuleDTO ruleDTO) {
        UnderwritingRule rule = underwritingService.createUnderwritingRule(ruleDTO);
        return ResponseEntity.ok(rule);
    }
    
    @PutMapping("/rules/{id}")
    @ApiOperation("更新核保规则")
    @PreAuthorize("hasRole('UNDERWRITER_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<UnderwritingRule> updateUnderwritingRule(@PathVariable Long id, 
                                                                 @RequestBody UnderwritingRuleDTO ruleDTO) {
        UnderwritingRule rule = underwritingService.updateUnderwritingRule(id, ruleDTO);
        return ResponseEntity.ok(rule);
    }
    
    @DeleteMapping("/rules/{id}")
    @ApiOperation("删除核保规则")
    @PreAuthorize("hasRole('UNDERWRITER_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<Void> deleteUnderwritingRule(@PathVariable Long id) {
        underwritingService.deleteUnderwritingRule(id);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/rules/product/{productId}")
    @ApiOperation("获取产品核保规则")
    @PreAuthorize("hasAnyRole('UNDERWRITER', 'UNDERWRITER_MANAGER', 'ADMIN')")
    public ResponseEntity<List<UnderwritingRule>> getRulesByProduct(@PathVariable Long productId) {
        List<UnderwritingRule> rules = underwritingService.getRulesByProduct(productId);
        return ResponseEntity.ok(rules);
    }
    
    @GetMapping("/statistics")
    @ApiOperation("获取核保统计")
    @PreAuthorize("hasAnyRole('UNDERWRITER_MANAGER', 'ADMIN')")
    public ResponseEntity<UnderwritingStatisticsDTO> getUnderwritingStatistics(DateRangeDTO dateRange) {
        UnderwritingStatisticsDTO statistics = underwritingService.getUnderwritingStatistics(dateRange);
        return ResponseEntity.ok(statistics);
    }
}

Service层

java 复制代码
public interface UnderwritingService {
    Underwriting initiateUnderwriting(Long policyId);
    Underwriting getUnderwritingById(Long id);
    Underwriting getUnderwritingByPolicyId(Long policyId);
    Page<Underwriting> searchUnderwritings(UnderwritingSearchDTO searchDTO, Pageable pageable);
    Underwriting makeDecision(UnderwritingDecisionDTO decisionDTO);
    Underwriting assignUnderwriting(Long id, String assignee);
    UnderwritingRule createUnderwritingRule(UnderwritingRuleDTO ruleDTO);
    UnderwritingRule updateUnderwritingRule(Long id, UnderwritingRuleDTO ruleDTO);
    void deleteUnderwritingRule(Long id);
    List<UnderwritingRule> getRulesByProduct(Long productId);
    UnderwritingStatus performAutoUnderwriting(Long policyId);
    UnderwritingStatisticsDTO getUnderwritingStatistics(DateRangeDTO dateRange);
}
java 复制代码
@Slf4j
@Service
@Transactional
public class UnderwritingServiceImpl implements UnderwritingService {
    
    private final UnderwritingRepository underwritingRepository;
    private final UnderwritingRuleRepository ruleRepository;
    private final PolicyRepository policyRepository;
    private final ProductRepository productRepository;
    private final ModelMapper modelMapper;
    private final DroolsUnderwritingEngine droolsEngine;
    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final RabbitTemplate rabbitTemplate;
    private final AuditLogService auditLogService;
    
    public UnderwritingServiceImpl(UnderwritingRepository underwritingRepository,
                                 UnderwritingRuleRepository ruleRepository,
                                 PolicyRepository policyRepository,
                                 ProductRepository productRepository,
                                 ModelMapper modelMapper,
                                 DroolsUnderwritingEngine droolsEngine,
                                 RuntimeService runtimeService,
                                 TaskService taskService,
                                 RabbitTemplate rabbitTemplate,
                                 AuditLogService auditLogService) {
        this.underwritingRepository = underwritingRepository;
        this.ruleRepository = ruleRepository;
        this.policyRepository = policyRepository;
        this.productRepository = productRepository;
        this.modelMapper = modelMapper;
        this.droolsEngine = droolsEngine;
        this.runtimeService = runtimeService;
        this.taskService = taskService;
        this.rabbitTemplate = rabbitTemplate;
        this.auditLogService = auditLogService;
    }

    @Override
    @Transactional
    public Underwriting initiateUnderwriting(Long policyId) {
        Policy policy = policyRepository.findById(policyId)
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        // 检查是否已有核保记录
        if (underwritingRepository.existsByPolicyId(policyId)) {
            throw new BusinessException("该保单已存在核保记录");
        }
        
        // 创建核保记录
        Underwriting underwriting = new Underwriting();
        underwriting.setPolicy(policy);
        underwriting.setStatus(UnderwritingStatus.PENDING);
        underwriting.setCreatedAt(new Date());
        underwriting.setUpdatedAt(new Date());
        
        Underwriting savedUnderwriting = underwritingRepository.save(underwriting);
        
        // 启动工作流
        startUnderwritingWorkflow(savedUnderwriting);
        
        // 记录审计日志
        auditLogService.logUnderwritingInitiation(savedUnderwriting);
        
        // 异步执行自动核保
        performAutoUnderwritingAsync(policyId);
        
        return savedUnderwriting;
    }

    @Override
    @Cacheable(value = "underwritings", key = "#id")
    public Underwriting getUnderwritingById(Long id) {
        return underwritingRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("核保记录不存在"));
    }

    @Override
    @Cacheable(value = "underwritings", key = "'policy_' + #policyId")
    public Underwriting getUnderwritingByPolicyId(Long policyId) {
        return underwritingRepository.findByPolicyId(policyId);
    }

    @Override
    public Page<Underwriting> searchUnderwritings(UnderwritingSearchDTO searchDTO, Pageable pageable) {
        return underwritingRepository.searchUnderwritings(
                searchDTO.getStatus(),
                searchDTO.getAssignedTo(),
                searchDTO.getStartDate(),
                searchDTO.getEndDate(),
                pageable);
    }

    @Override
    @CacheEvict(value = "underwritings", key = "#decisionDTO.underwritingId")
    public Underwriting makeDecision(UnderwritingDecisionDTO decisionDTO) {
        Underwriting underwriting = underwritingRepository.findById(decisionDTO.getUnderwritingId())
                .orElseThrow(() -> new ResourceNotFoundException("核保记录不存在"));
        
        // 检查状态是否可以做出决策
        if (underwriting.getStatus() != UnderwritingStatus.IN_PROGRESS && 
            underwriting.getStatus() != UnderwritingStatus.REFERRED) {
            throw new BusinessException("当前状态不能做出核保决策");
        }
        
        underwriting.setStatus(decisionDTO.getDecision());
        underwriting.setDecisionReason(decisionDTO.getReason());
        underwriting.setNotes(decisionDTO.getNotes());
        underwriting.setCompletedAt(new Date());
        underwriting.setUpdatedAt(new Date());
        
        Underwriting updatedUnderwriting = underwritingRepository.save(underwriting);
        
        // 完成工作流任务
        completeWorkflowTask(updatedUnderwriting);
        
        // 根据决策结果处理保单
        handlePolicyAfterDecision(updatedUnderwriting);
        
        // 记录审计日志
        auditLogService.logUnderwritingDecision(updatedUnderwriting);
        
        return updatedUnderwriting;
    }

    @Override
    @CacheEvict(value = "underwritings", key = "#id")
    public Underwriting assignUnderwriting(Long id, String assignee) {
        Underwriting underwriting = underwritingRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("核保记录不存在"));
        
        // 检查状态是否可以分配
        if (underwriting.getStatus() != UnderwritingStatus.PENDING && 
            underwriting.getStatus() != UnderwritingStatus.REFERRED) {
            throw new BusinessException("当前状态不能分配核保任务");
        }
        
        underwriting.setAssignedTo(assignee);
        underwriting.setStatus(UnderwritingStatus.IN_PROGRESS);
        underwriting.setUpdatedAt(new Date());
        
        Underwriting updatedUnderwriting = underwritingRepository.save(underwriting);
        
        // 更新工作流任务
        updateWorkflowAssignment(updatedUnderwriting);
        
        return updatedUnderwriting;
    }

    @Override
    public UnderwritingRule createUnderwritingRule(UnderwritingRuleDTO ruleDTO) {
        Product product = productRepository.findById(ruleDTO.getProductId())
                .orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
        
        UnderwritingRule rule = modelMapper.map(ruleDTO, UnderwritingRule.class);
        rule.setProduct(product);
        
        return ruleRepository.save(rule);
    }

    @Override
    public UnderwritingRule updateUnderwritingRule(Long id, UnderwritingRuleDTO ruleDTO) {
        UnderwritingRule existingRule = ruleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("核保规则不存在"));
        
        modelMapper.map(ruleDTO, existingRule);
        
        return ruleRepository.save(existingRule);
    }

    @Override
    public void deleteUnderwritingRule(Long id) {
        ruleRepository.deleteById(id);
    }

    @Override
    public List<UnderwritingRule> getRulesByProduct(Long productId) {
        return ruleRepository.findByProductId(productId);
    }

    @Override
    @Transactional
    public UnderwritingStatus performAutoUnderwriting(Long policyId) {
        Policy policy = policyRepository.findById(policyId)
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        Underwriting underwriting = underwritingRepository.findByPolicyId(policyId);
        if (underwriting == null) {
            underwriting = initiateUnderwriting(policyId);
        }
        
        // 获取产品相关的激活规则
        List<UnderwritingRule> rules = ruleRepository.findByProductIdAndIsActiveTrue(
                policy.getProduct().getId());
        
        // 执行自动核保
        UnderwritingDecision decision = droolsEngine.evaluate(policy, rules);
        
        // 更新核保记录
        underwriting.setType(UnderwritingType.AUTO);
        underwriting.setStatus(decision.getStatus());
        underwriting.setDecisionReason(decision.getReason());
        underwriting.setUpdatedAt(new Date());
        
        if (decision.getStatus() != UnderwritingStatus.PENDING && 
            decision.getStatus() != UnderwritingStatus.REFERRED) {
            underwriting.setCompletedAt(new Date());
        }
        
        Underwriting updatedUnderwriting = underwritingRepository.save(underwriting);
        
        // 根据决策结果处理
        if (decision.getStatus() == UnderwritingStatus.APPROVED) {
            // 自动核保通过,更新保单状态
            policy.setStatus(PolicyStatus.UNDERWRITTEN);
            policyRepository.save(policy);
            
            // 触发后续流程
            rabbitTemplate.convertAndSend("policy.underwritten.queue", policyId);
        } else if (decision.getStatus() == UnderwritingStatus.REFERRED) {
            // 需要人工核保
            rabbitTemplate.convertAndSend("underwriting.manual.queue", policyId);
        }
        
        return decision.getStatus();
    }

    @Override
    public UnderwritingStatisticsDTO getUnderwritingStatistics(DateRangeDTO dateRange) {
        UnderwritingStatisticsDTO statistics = new UnderwritingStatisticsDTO();
        
        // 获取已通过的核保数量
        statistics.setApprovedCount(
                underwritingRepository.countApprovedBetween(dateRange.getStartDate(), dateRange.getEndDate()));
        
        // 其他统计指标...
        
        return statistics;
    }
    
    private void startUnderwritingWorkflow(Underwriting underwriting) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("underwritingId", underwriting.getId());
        variables.put("policyId", underwriting.getPolicy().getId());
        variables.put("productCode", underwriting.getPolicy().getProduct().getCode());
        
        runtimeService.startProcessInstanceByKey("underwritingProcess", variables);
    }
    
    private void completeWorkflowTask(Underwriting underwriting) {
        Task task = taskService.createTaskQuery()
                .processInstanceBusinessKey(underwriting.getId().toString())
                .singleResult();
        
        if (task != null) {
            Map<String, Object> variables = new HashMap<>();
            variables.put("decision", underwriting.getStatus().name());
            variables.put("decisionReason", underwriting.getDecisionReason());
            
            taskService.complete(task.getId(), variables);
        }
    }
    
    private void updateWorkflowAssignment(Underwriting underwriting) {
        Task task = taskService.createTaskQuery()
                .processInstanceBusinessKey(underwriting.getId().toString())
                .singleResult();
        
        if (task != null) {
            taskService.setAssignee(task.getId(), underwriting.getAssignedTo());
        }
    }
    
    private void handlePolicyAfterDecision(Underwriting underwriting) {
        Policy policy = underwriting.getPolicy();
        
        switch (underwriting.getStatus()) {
            case APPROVED:
                policy.setStatus(PolicyStatus.UNDERWRITTEN);
                rabbitTemplate.convertAndSend("policy.underwritten.queue", policy.getId());
                break;
            case DECLINED:
                policy.setStatus(PolicyStatus.DECLINED);
                rabbitTemplate.convertAndSend("policy.declined.queue", policy.getId());
                break;
            case REFERRED:
                rabbitTemplate.convertAndSend("underwriting.referred.queue", policy.getId());
                break;
        }
        
        policyRepository.save(policy);
    }
    
    @Async
    protected void performAutoUnderwritingAsync(Long policyId) {
        try {
            performAutoUnderwriting(policyId);
        } catch (Exception e) {
            log.error("自动核保失败: {}", policyId, e);
        }
    }
}

Repository层

java 复制代码
public interface UnderwritingRepository extends JpaRepository<Underwriting, Long> {
    
    Underwriting findByPolicyId(Long policyId);
    
    List<Underwriting> findByStatus(UnderwritingStatus status);
    
    List<Underwriting> findByAssignedTo(String assignedTo);
    
    @Query("SELECT u FROM Underwriting u WHERE " +
           "u.status = :status AND " +
           "(:assignedTo IS NULL OR u.assignedTo = :assignedTo) AND " +
           "(:startDate IS NULL OR u.createdAt >= :startDate) AND " +
           "(:endDate IS NULL OR u.createdAt <= :endDate)")
    Page<Underwriting> searchUnderwritings(@Param("status") UnderwritingStatus status,
                                        @Param("assignedTo") String assignedTo,
                                        @Param("startDate") Date startDate,
                                        @Param("endDate") Date endDate,
                                        Pageable pageable);
    
    @Query("SELECT COUNT(u) FROM Underwriting u WHERE u.status = 'APPROVED' AND u.completedAt BETWEEN :start AND :end")
    Long countApprovedBetween(@Param("start") Date start, @Param("end") Date end);
}
java 复制代码
public interface UnderwritingRuleRepository extends JpaRepository<UnderwritingRule, Long> {
    
    List<UnderwritingRule> findByProductId(Long productId);
    
    List<UnderwritingRule> findByProductIdAndIsActiveTrue(Long productId);
}

5. 理赔管理

理赔申请受理

理赔调查与审核

理赔计算与支付

欺诈检测

yml 复制代码
# MinIO配置
minio:
  endpoint: http://localhost:9000
  access-key: minioadmin
  secret-key: minioadmin
  bucket-name: insurance-claims

# 缓存配置
cache:
  claim:
    ttl: 86400 # 24小时

# Activiti配置
activiti:
  check-process-definitions: true
  database-schema-update: true
  history-level: full
  async-executor-activate: true
java 复制代码
@Component
public class ClaimNumberGenerator {
    
    private static final String PREFIX = "CLM";
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    private static int sequence = 0;
    private static final int MAX_SEQUENCE = 9999;
    
    public synchronized String generateClaimNo(String policyNo) {
        String datePart = dateFormat.format(new Date());
        
        if (sequence >= MAX_SEQUENCE) {
            sequence = 0;
        }
        sequence++;
        
        String sequencePart = String.format("%04d", sequence);
        
        return PREFIX + policyNo.substring(3, 7) + datePart + sequencePart;
    }
}
java 复制代码
@Service
public class FileStorageService {
    
    private final MinioClient minioClient;
    
    @Value("${minio.bucket-name}")
    private String bucketName;
    
    public FileStorageService(MinioClient minioClient) {
        this.minioClient = minioClient;
    }
    
    public String storeFile(MultipartFile file) {
        try {
            // 确保存储桶存在
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
            
            // 生成唯一文件名
            String fileName = UUID.randomUUID().toString() + "-" + file.getOriginalFilename();
            
            // 上传文件
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .stream(file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build());
            
            return fileName;
        } catch (Exception e) {
            throw new RuntimeException("文件存储失败", e);
        }
    }
    
    public byte[] getFile(String fileName) {
        try {
            GetObjectResponse response = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build());
            
            return response.readAllBytes();
        } catch (Exception e) {
            throw new RuntimeException("文件读取失败", e);
        }
    }
}

Controller层

java 复制代码
@RestController
@RequestMapping("/api/claims")
@Api(tags = "理赔管理")
public class ClaimController {
    
    private final ClaimService claimService;
    
    public ClaimController(ClaimService claimService) {
        this.claimService = claimService;
    }
    
    @PostMapping
    @ApiOperation("创建理赔")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<Claim> createClaim(@RequestBody ClaimDTO claimDTO) {
        Claim claim = claimService.createClaim(claimDTO);
        return ResponseEntity.ok(claim);
    }
    
    @PutMapping("/{id}")
    @ApiOperation("更新理赔")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<Claim> updateClaim(@PathVariable Long id, 
                                           @RequestBody ClaimDTO claimDTO) {
        Claim claim = claimService.updateClaim(id, claimDTO);
        return ResponseEntity.ok(claim);
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取理赔详情")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<Claim> getClaimById(@PathVariable Long id) {
        Claim claim = claimService.getClaimById(id);
        return ResponseEntity.ok(claim);
    }
    
    @GetMapping("/number/{claimNo}")
    @ApiOperation("通过理赔单号获取理赔")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<Claim> getClaimByNo(@PathVariable String claimNo) {
        Claim claim = claimService.getClaimByNo(claimNo);
        return ResponseEntity.ok(claim);
    }
    
    @GetMapping("/search")
    @ApiOperation("搜索理赔")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<Page<Claim>> searchClaims(ClaimSearchDTO searchDTO, 
                                                  Pageable pageable) {
        Page<Claim> claims = claimService.searchClaims(searchDTO, pageable);
        return ResponseEntity.ok(claims);
    }
    
    @GetMapping("/policy/{policyId}")
    @ApiOperation("获取保单的理赔记录")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<List<Claim>> getClaimsByPolicy(@PathVariable Long policyId) {
        List<Claim> claims = claimService.getClaimsByPolicy(policyId);
        return ResponseEntity.ok(claims);
    }
    
    @PostMapping("/{claimId}/submit")
    @ApiOperation("提交理赔")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<Claim> submitClaim(@PathVariable Long claimId) {
        Claim claim = claimService.submitClaim(claimId);
        return ResponseEntity.ok(claim);
    }
    
    @PostMapping("/approve")
    @ApiOperation("核准理赔")
    @PreAuthorize("hasRole('CLAIM_APPROVER') or hasRole('ADMIN')")
    public ResponseEntity<Claim> approveClaim(@RequestBody ClaimApprovalDTO approvalDTO) {
        Claim claim = claimService.approveClaim(approvalDTO);
        return ResponseEntity.ok(claim);
    }
    
    @PostMapping("/{claimId}/investigate")
    @ApiOperation("开始理赔调查")
    @PreAuthorize("hasRole('CLAIM_INVESTIGATOR') or hasRole('ADMIN')")
    public ResponseEntity<Claim> startInvestigation(@PathVariable Long claimId) {
        Claim claim = claimService.startInvestigation(claimId);
        return ResponseEntity.ok(claim);
    }
    
    @PostMapping("/investigation")
    @ApiOperation("添加调查记录")
    @PreAuthorize("hasRole('CLAIM_INVESTIGATOR') or hasRole('ADMIN')")
    public ResponseEntity<Claim> addInvestigation(@RequestBody ClaimInvestigationDTO investigationDTO) {
        Claim claim = claimService.addInvestigation(investigationDTO);
        return ResponseEntity.ok(claim);
    }
    
    @PostMapping("/{claimId}/payment")
    @ApiOperation("处理理赔付款")
    @PreAuthorize("hasRole('CLAIM_PAYMENT') or hasRole('ADMIN')")
    public ResponseEntity<Claim> processPayment(@PathVariable Long claimId) {
        Claim claim = claimService.processPayment(claimId);
        return ResponseEntity.ok(claim);
    }
    
    @GetMapping("/export")
    @ApiOperation("导出理赔Excel")
    @PreAuthorize("hasRole('CLAIM_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<byte[]> exportClaimsToExcel(DateRangeDTO dateRange) {
        byte[] excelData = claimService.exportClaimsToExcel(dateRange);
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=claims.xlsx")
                .body(excelData);
    }
    
    @PostMapping("/{claimId}/documents")
    @ApiOperation("上传理赔材料")
    @PreAuthorize("hasAnyRole('CLAIM', 'ADMIN')")
    public ResponseEntity<ClaimDocument> uploadDocument(
            @PathVariable Long claimId,
            @RequestParam("file") MultipartFile file,
            @RequestParam String documentType) {
        ClaimDocument document = claimService.uploadDocument(claimId, file, documentType);
        return ResponseEntity.ok(document);
    }
    
    @GetMapping("/statistics")
    @ApiOperation("获取理赔统计")
    @PreAuthorize("hasRole('CLAIM_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<ClaimStatisticsDTO> getClaimStatistics(DateRangeDTO dateRange) {
        ClaimStatisticsDTO statistics = claimService.getClaimStatistics(dateRange);
        return ResponseEntity.ok(statistics);
    }
}

Service层

java 复制代码
public interface ClaimService {
    Claim createClaim(ClaimDTO claimDTO);
    Claim updateClaim(Long id, ClaimDTO claimDTO);
    Claim getClaimById(Long id);
    Claim getClaimByNo(String claimNo);
    Page<Claim> searchClaims(ClaimSearchDTO searchDTO, Pageable pageable);
    List<Claim> getClaimsByPolicy(Long policyId);
    Claim submitClaim(Long claimId);
    Claim approveClaim(ClaimApprovalDTO approvalDTO);
    Claim startInvestigation(Long claimId);
    Claim addInvestigation(ClaimInvestigationDTO investigationDTO);
    Claim processPayment(Long claimId);
    byte[] exportClaimsToExcel(DateRangeDTO dateRange);
    ClaimDocument uploadDocument(Long claimId, MultipartFile file, String documentType);
    ClaimStatisticsDTO getClaimStatistics(DateRangeDTO dateRange);
}
java 复制代码
@Slf4j
@Service
@Transactional
public class ClaimServiceImpl implements ClaimService {
    
    private final ClaimRepository claimRepository;
    private final PolicyRepository policyRepository;
    private final ClaimDocumentRepository documentRepository;
    private final ClaimBeneficiaryRepository beneficiaryRepository;
    private final ClaimInvestigationRepository investigationRepository;
    private final ModelMapper modelMapper;
    private final ClaimNumberGenerator claimNumberGenerator;
    private final FileStorageService fileStorageService;
    private final RabbitTemplate rabbitTemplate;
    private final AuditLogService auditLogService;
    
    public ClaimServiceImpl(ClaimRepository claimRepository,
                          PolicyRepository policyRepository,
                          ClaimDocumentRepository documentRepository,
                          ClaimBeneficiaryRepository beneficiaryRepository,
                          ClaimInvestigationRepository investigationRepository,
                          ModelMapper modelMapper,
                          ClaimNumberGenerator claimNumberGenerator,
                          FileStorageService fileStorageService,
                          RabbitTemplate rabbitTemplate,
                          AuditLogService auditLogService) {
        this.claimRepository = claimRepository;
        this.policyRepository = policyRepository;
        this.documentRepository = documentRepository;
        this.beneficiaryRepository = beneficiaryRepository;
        this.investigationRepository = investigationRepository;
        this.modelMapper = modelMapper;
        this.claimNumberGenerator = claimNumberGenerator;
        this.fileStorageService = fileStorageService;
        this.rabbitTemplate = rabbitTemplate;
        this.auditLogService = auditLogService;
    }

    @Override
    @CacheEvict(value = "claims", allEntries = true)
    public Claim createClaim(ClaimDTO claimDTO) {
        Policy policy = policyRepository.findById(claimDTO.getPolicyId())
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        // 验证保单状态是否可以理赔
        if (policy.getStatus() != PolicyStatus.ACTIVE && 
            policy.getStatus() != PolicyStatus.UNDERWRITTEN) {
            throw new BusinessException("当前保单状态不能申请理赔");
        }
        
        // 生成理赔单号
        String claimNo = claimNumberGenerator.generateClaimNo(policy.getPolicyNo());
        
        // 创建理赔记录
        Claim claim = modelMapper.map(claimDTO, Claim.class);
        claim.setClaimNo(claimNo);
        claim.setPolicy(policy);
        claim.setStatus(ClaimStatus.DRAFT);
        claim.setCreatedAt(new Date());
        claim.setUpdatedAt(new Date());
        
        Claim savedClaim = claimRepository.save(claim);
        
        // 保存理赔材料
        saveDocuments(claimDTO, savedClaim);
        
        // 保存理赔受益人
        saveBeneficiaries(claimDTO, savedClaim);
        
        // 记录审计日志
        auditLogService.logClaimCreation(savedClaim);
        
        return savedClaim;
    }

    @Override
    @CacheEvict(value = "claims", key = "#id")
    public Claim updateClaim(Long id, ClaimDTO claimDTO) {
        Claim existingClaim = claimRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以修改
        if (existingClaim.getStatus() != ClaimStatus.DRAFT) {
            throw new BusinessException("只有草稿状态的理赔可以修改");
        }
        
        modelMapper.map(claimDTO, existingClaim);
        existingClaim.setUpdatedAt(new Date());
        
        // 更新理赔材料
        updateDocuments(claimDTO, existingClaim);
        
        // 更新理赔受益人
        updateBeneficiaries(claimDTO, existingClaim);
        
        return claimRepository.save(existingClaim);
    }

    @Override
    @Cacheable(value = "claims", key = "#id")
    public Claim getClaimById(Long id) {
        return claimRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
    }

    @Override
    @Cacheable(value = "claims", key = "#claimNo")
    public Claim getClaimByNo(String claimNo) {
        return claimRepository.findByClaimNo(claimNo)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
    }

    @Override
    public Page<Claim> searchClaims(ClaimSearchDTO searchDTO, Pageable pageable) {
        return claimRepository.searchClaims(
                searchDTO.getClaimNo(),
                searchDTO.getPolicyNo(),
                searchDTO.getStatus(),
                searchDTO.getStartDate(),
                searchDTO.getEndDate(),
                pageable);
    }

    @Override
    public List<Claim> getClaimsByPolicy(Long policyId) {
        return claimRepository.findByPolicyId(policyId);
    }

    @Override
    @CacheEvict(value = "claims", key = "#claimId")
    public Claim submitClaim(Long claimId) {
        Claim claim = claimRepository.findById(claimId)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以提交
        if (claim.getStatus() != ClaimStatus.DRAFT) {
            throw new BusinessException("只有草稿状态的理赔可以提交");
        }
        
        // 验证必要材料是否齐全
        validateRequiredDocuments(claim);
        
        claim.setStatus(ClaimStatus.REPORTED);
        claim.setUpdatedAt(new Date());
        
        Claim updatedClaim = claimRepository.save(claim);
        
        // 启动理赔工作流
        startClaimWorkflow(updatedClaim);
        
        // 记录审计日志
        auditLogService.logClaimSubmission(updatedClaim);
        
        return updatedClaim;
    }

    @Override
    @CacheEvict(value = "claims", key = "#approvalDTO.claimId")
    public Claim approveClaim(ClaimApprovalDTO approvalDTO) {
        Claim claim = claimRepository.findById(approvalDTO.getClaimId())
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以核准
        if (claim.getStatus() != ClaimStatus.UNDER_REVIEW && 
            claim.getStatus() != ClaimStatus.INVESTIGATING) {
            throw new BusinessException("当前状态不能核准理赔");
        }
        
        claim.setStatus(approvalDTO.getDecision());
        claim.setApprovedAmount(approvalDTO.getApprovedAmount());
        claim.setApprovalDate(new Date());
        claim.setUpdatedAt(new Date());
        
        Claim updatedClaim = claimRepository.save(claim);
        
        // 如果是核准状态,准备付款
        if (approvalDTO.getDecision() == ClaimStatus.APPROVED || 
            approvalDTO.getDecision() == ClaimStatus.PARTIALLY_APPROVED) {
            rabbitTemplate.convertAndSend("claim.payment.queue", updatedClaim.getId());
        }
        
        // 记录审计日志
        auditLogService.logClaimApproval(updatedClaim);
        
        return updatedClaim;
    }

    @Override
    @CacheEvict(value = "claims", key = "#claimId")
    public Claim startInvestigation(Long claimId) {
        Claim claim = claimRepository.findById(claimId)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以开始调查
        if (claim.getStatus() != ClaimStatus.REPORTED && 
            claim.getStatus() != ClaimStatus.UNDER_REVIEW) {
            throw new BusinessException("当前状态不能开始调查");
        }
        
        claim.setStatus(ClaimStatus.INVESTIGATING);
        claim.setUpdatedAt(new Date());
        
        Claim updatedClaim = claimRepository.save(claim);
        
        // 发送调查通知
        rabbitTemplate.convertAndSend("claim.investigation.queue", updatedClaim.getId());
        
        return updatedClaim;
    }

    @Override
    @CacheEvict(value = "claims", key = "#investigationDTO.claimId")
    public Claim addInvestigation(ClaimInvestigationDTO investigationDTO) {
        Claim claim = claimRepository.findById(investigationDTO.getClaimId())
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以添加调查
        if (claim.getStatus() != ClaimStatus.INVESTIGATING) {
            throw new BusinessException("当前状态不能添加调查记录");
        }
        
        ClaimInvestigation investigation = modelMapper.map(investigationDTO, ClaimInvestigation.class);
        investigation.setClaim(claim);
        investigation.setCreatedAt(new Date());
        
        investigationRepository.save(investigation);
        
        // 调查完成后自动转回审核状态
        claim.setStatus(ClaimStatus.UNDER_REVIEW);
        claim.setUpdatedAt(new Date());
        
        Claim updatedClaim = claimRepository.save(claim);
        
        return updatedClaim;
    }

    @Override
    @CacheEvict(value = "claims", key = "#claimId")
    public Claim processPayment(Long claimId) {
        Claim claim = claimRepository.findById(claimId)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以付款
        if (claim.getStatus() != ClaimStatus.APPROVED && 
            claim.getStatus() != ClaimStatus.PARTIALLY_APPROVED) {
            throw new BusinessException("只有核准状态的理赔可以付款");
        }
        
        // 这里应该有实际的付款逻辑
        // 简化版示例:假设付款成功
        
        claim.setStatus(ClaimStatus.PAID);
        claim.setUpdatedAt(new Date());
        
        Claim updatedClaim = claimRepository.save(claim);
        
        // 更新保单状态
        Policy policy = claim.getPolicy();
        policy.setStatus(PolicyStatus.CLAIMED);
        policyRepository.save(policy);
        
        // 记录审计日志
        auditLogService.logClaimPayment(updatedClaim);
        
        return updatedClaim;
    }

    @Override
    public byte[] exportClaimsToExcel(DateRangeDTO dateRange) {
        try (Workbook workbook = new XSSFWorkbook(); 
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            
            Sheet sheet = workbook.createSheet("理赔列表");
            
            // 创建标题行
            Row headerRow = sheet.createRow(0);
            String[] headers = {"理赔单号", "保单号", "状态", "事故日期", "报案日期", "索赔金额", "核定金额", "核定日期"};
            for (int i = 0; i < headers.length; i++) {
                headerRow.createCell(i).setCellValue(headers[i]);
            }
            
            // 填充数据
            List<Claim> claims = claimRepository.findAllByApprovalDateBetween(
                    dateRange.getStartDate(), dateRange.getEndDate());
            
            int rowNum = 1;
            for (Claim claim : claims) {
                Row row = sheet.createRow(rowNum++);
                row.createCell(0).setCellValue(claim.getClaimNo());
                row.createCell(1).setCellValue(claim.getPolicy().getPolicyNo());
                row.createCell(2).setCellValue(claim.getStatus().name());
                row.createCell(3).setCellValue(claim.getIncidentDate().toString());
                row.createCell(4).setCellValue(claim.getReportDate().toString());
                row.createCell(5).setCellValue(claim.getClaimedAmount().doubleValue());
                row.createCell(6).setCellValue(
                        claim.getApprovedAmount() != null ? claim.getApprovedAmount().doubleValue() : 0);
                row.createCell(7).setCellValue(
                        claim.getApprovalDate() != null ? claim.getApprovalDate().toString() : "");
            }
            
            workbook.write(out);
            return out.toByteArray();
        } catch (IOException e) {
            log.error("导出理赔Excel失败", e);
            throw new BusinessException("导出理赔Excel失败");
        }
    }

    @Override
    public ClaimDocument uploadDocument(Long claimId, MultipartFile file, String documentType) {
        Claim claim = claimRepository.findById(claimId)
                .orElseThrow(() -> new ResourceNotFoundException("理赔记录不存在"));
        
        // 检查是否可以上传材料
        if (claim.getStatus() == ClaimStatus.PAID || claim.getStatus() == ClaimStatus.CLOSED) {
            throw new BusinessException("当前状态不能上传材料");
        }
        
        // 存储文件
        String filePath = fileStorageService.storeFile(file);
        
        // 创建文档记录
        ClaimDocument document = new ClaimDocument();
        document.setClaim(claim);
        document.setDocumentType(documentType);
        document.setFileName(file.getOriginalFilename());
        document.setFilePath(filePath);
        document.setFileSize(file.getSize());
        document.setFileType(file.getContentType());
        
        return documentRepository.save(document);
    }

    @Override
    public ClaimStatisticsDTO getClaimStatistics(DateRangeDTO dateRange) {
        ClaimStatisticsDTO statistics = new ClaimStatisticsDTO();
        
        // 获取已赔付理赔数量
        statistics.setPaidClaimCount(
                claimRepository.countPaidClaimsBetween(dateRange.getStartDate(), dateRange.getEndDate()));
        
        // 获取已赔付总金额
        BigDecimal paidAmount = claimRepository.sumPaidAmountBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setTotalPaidAmount(paidAmount != null ? paidAmount : BigDecimal.ZERO);
        
        // 其他统计指标...
        
        return statistics;
    }
    
    private void saveDocuments(ClaimDTO claimDTO, Claim claim) {
        for (ClaimDTO.ClaimDocumentDTO documentDTO : claimDTO.getDocuments()) {
            ClaimDocument document = modelMapper.map(documentDTO, ClaimDocument.class);
            document.setClaim(claim);
            documentRepository.save(document);
        }
    }
    
    private void updateDocuments(ClaimDTO claimDTO, Claim claim) {
        // 删除旧文档记录(实际文件不删除)
        documentRepository.deleteByClaimId(claim.getId());
        
        // 添加新文档记录
        saveDocuments(claimDTO, claim);
    }
    
    private void saveBeneficiaries(ClaimDTO claimDTO, Claim claim) {
        for (ClaimDTO.ClaimBeneficiaryDTO beneficiaryDTO : claimDTO.getBeneficiaries()) {
            ClaimBeneficiary beneficiary = modelMapper.map(beneficiaryDTO, ClaimBeneficiary.class);
            beneficiary.setClaim(claim);
            beneficiaryRepository.save(beneficiary);
        }
    }
    
    private void updateBeneficiaries(ClaimDTO claimDTO, Claim claim) {
        // 删除旧受益人
        beneficiaryRepository.deleteByClaimId(claim.getId());
        
        // 添加新受益人
        saveBeneficiaries(claimDTO, claim);
    }
    
    private void validateRequiredDocuments(Claim claim) {
        // 这里应该有业务规则验证必要材料是否齐全
        // 简化版示例:至少需要一个材料
        if (documentRepository.countByClaimId(claim.getId()) == 0) {
            throw new BusinessException("必须上传至少一个理赔材料");
        }
    }
    
    private void startClaimWorkflow(Claim claim) {
        // 这里应该有启动Activiti工作流的逻辑
        // 简化版示例:发送到消息队列
        rabbitTemplate.convertAndSend("claim.process.queue", claim.getId());
    }
}

Repository层

java 复制代码
ublic interface ClaimRepository extends JpaRepository<Claim, Long> {
    
    Claim findByClaimNo(String claimNo);
    
    List<Claim> findByPolicyId(Long policyId);
    
    List<Claim> findByStatus(ClaimStatus status);
    
    @Query("SELECT c FROM Claim c WHERE " +
           "(:claimNo IS NULL OR c.claimNo LIKE %:claimNo%) AND " +
           "(:policyNo IS NULL OR c.policy.policyNo LIKE %:policyNo%) AND " +
           "(:status IS NULL OR c.status = :status) AND " +
           "(:startDate IS NULL OR c.incidentDate >= :startDate) AND " +
           "(:endDate IS NULL OR c.incidentDate <= :endDate)")
    Page<Claim> searchClaims(@Param("claimNo") String claimNo,
                           @Param("policyNo") String policyNo,
                           @Param("status") ClaimStatus status,
                           @Param("startDate") Date startDate,
                           @Param("endDate") Date endDate,
                           Pageable pageable);
    
    @Query("SELECT COUNT(c) FROM Claim c WHERE c.status = 'PAID' AND c.approvalDate BETWEEN :start AND :end")
    Long countPaidClaimsBetween(@Param("start") Date start, @Param("end") Date end);
    
    @Query("SELECT SUM(c.approvedAmount) FROM Claim c WHERE c.status = 'PAID' AND c.approvalDate BETWEEN :start AND :end")
    BigDecimal sumPaidAmountBetween(@Param("start") Date start, @Param("end") Date end);
}
java 复制代码
public interface ClaimDocumentRepository extends JpaRepository<ClaimDocument, Long> {
    
    List<ClaimDocument> findByClaimId(Long claimId);
    
    List<ClaimDocument> findByClaimIdAndDocumentType(Long claimId, String documentType);
}

6. 收付费管理

保费收取与对账

佣金计算与发放

赔款支付

财务报表

yml 复制代码
# 支付网关配置
payment:
  alipay:
    app-id: your-app-id
    merchant-private-key: your-private-key
    alipay-public-key: your-public-key
    gateway-url: https://openapi.alipay.com/gateway.do
  wechat:
    app-id: your-app-id
    mch-id: your-mch-id
    api-key: your-api-key
    cert-path: classpath:cert/apiclient_cert.p12

# 缓存配置
cache:
  payment:
    ttl: 86400 # 24小时
java 复制代码
@Component
@Slf4j
public class ReconciliationProcessor {
    
    private final PaymentRepository paymentRepository;
    
    public ReconciliationProcessor(PaymentRepository paymentRepository) {
        this.paymentRepository = paymentRepository;
    }
    
    public List<Payment> processReconciliationFile(String bankName, MultipartFile file) {
        try {
            String extension = FilenameUtils.getExtension(file.getOriginalFilename());
            
            if ("xls".equalsIgnoreCase(extension) || "xlsx".equalsIgnoreCase(extension)) {
                return processExcelReconciliation(bankName, file);
            } else if ("txt".equalsIgnoreCase(extension) || "csv".equalsIgnoreCase(extension)) {
                return processTextReconciliation(bankName, file);
            } else {
                throw new IllegalArgumentException("不支持的文件类型");
            }
        } catch (IOException e) {
            log.error("对账文件处理失败", e);
            throw new RuntimeException("对账文件处理失败", e);
        }
    }
    
    private List<Payment> processExcelReconciliation(String bankName, MultipartFile file) throws IOException {
        List<Payment> matchedPayments = new ArrayList<>();
        
        Workbook workbook = WorkbookFactory.create(file.getInputStream());
        Sheet sheet = workbook.getSheetAt(0);
        
        for (Row row : sheet) {
            if (row.getRowNum() == 0) continue; // 跳过标题行
            
            // 解析银行交易记录 (根据实际银行文件格式调整)
            String transactionId = row.getCell(0).getStringCellValue();
            BigDecimal amount = BigDecimal.valueOf(row.getCell(1).getNumericCellValue());
            Date transactionDate = row.getCell(2).getDateCellValue();
            
            // 查找匹配的支付记录
            Payment payment = paymentRepository.findByTransactionId(transactionId);
            if (payment != null && payment.getAmount().compareTo(amount) == 0) {
                matchedPayments.add(payment);
            }
        }
        
        workbook.close();
        return matchedPayments;
    }
    
    private List<Payment> processTextReconciliation(String bankName, MultipartFile file) throws IOException {
        // 处理文本格式的对账文件
        // 实现逻辑...
        return new ArrayList<>();
    }
}
java 复制代码
@Component
public class PaymentNumberGenerator {
    
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    private static int sequence = 0;
    private static final int MAX_SEQUENCE = 9999;
    
    public synchronized String generatePaymentNo(String policyNo, PaymentType type) {
        String prefix;
        switch (type) {
            case PREMIUM:
                prefix = "P";
                break;
            case REFUND:
                prefix = "R";
                break;
            case CLAIM:
                prefix = "C";
                break;
            default:
                prefix = "O";
        }
        
        String datePart = dateFormat.format(new Date());
        
        if (sequence >= MAX_SEQUENCE) {
            sequence = 0;
        }
        sequence++;
        
        String sequencePart = String.format("%04d", sequence);
        
        return prefix + policyNo.substring(3, 7) + datePart + sequencePart;
    }
}

Controller层

java 复制代码
@RestController
@RequestMapping("/api/payments")
@Api(tags = "收付费管理")
public class PaymentController {
    
    private final PaymentService paymentService;
    
    public PaymentController(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    @PostMapping
    @ApiOperation("创建支付记录")
    @PreAuthorize("hasRole('FINANCE') or hasRole('ADMIN')")
    public ResponseEntity<Payment> createPayment(@RequestBody PaymentDTO paymentDTO) {
        Payment payment = paymentService.createPayment(paymentDTO);
        return ResponseEntity.ok(payment);
    }
    
    @PutMapping("/{id}")
    @ApiOperation("更新支付记录")
    @PreAuthorize("hasRole('FINANCE') or hasRole('ADMIN')")
    public ResponseEntity<Payment> updatePayment(@PathVariable Long id, 
                                               @RequestBody PaymentDTO paymentDTO) {
        Payment payment = paymentService.updatePayment(id, paymentDTO);
        return ResponseEntity.ok(payment);
    }
    
    @GetMapping("/{id}")
    @ApiOperation("获取支付记录详情")
    @PreAuthorize("hasAnyRole('FINANCE', 'AGENT', 'ADMIN')")
    public ResponseEntity<Payment> getPaymentById(@PathVariable Long id) {
        Payment payment = paymentService.getPaymentById(id);
        return ResponseEntity.ok(payment);
    }
    
    @GetMapping("/number/{paymentNo}")
    @ApiOperation("通过支付流水号获取支付记录")
    @PreAuthorize("hasAnyRole('FINANCE', 'AGENT', 'ADMIN')")
    public ResponseEntity<Payment> getPaymentByNo(@PathVariable String paymentNo) {
        Payment payment = paymentService.getPaymentByNo(paymentNo);
        return ResponseEntity.ok(payment);
    }
    
    @GetMapping("/search")
    @ApiOperation("搜索支付记录")
    @PreAuthorize("hasAnyRole('FINANCE', 'AGENT', 'ADMIN')")
    public ResponseEntity<Page<Payment>> searchPayments(PaymentSearchDTO searchDTO, 
                                                      Pageable pageable) {
        Page<Payment> payments = paymentService.searchPayments(searchDTO, pageable);
        return ResponseEntity.ok(payments);
    }
    
    @GetMapping("/policy/{policyId}")
    @ApiOperation("获取保单的支付记录")
    @PreAuthorize("hasAnyRole('FINANCE', 'AGENT', 'ADMIN')")
    public ResponseEntity<List<Payment>> getPaymentsByPolicy(@PathVariable Long policyId) {
        List<Payment> payments = paymentService.getPaymentsByPolicy(policyId);
        return ResponseEntity.ok(payments);
    }
    
    @PostMapping("/initiate")
    @ApiOperation("发起支付")
    @PreAuthorize("hasAnyRole('FINANCE', 'AGENT', 'ADMIN')")
    public ResponseEntity<Payment> initiatePayment(@RequestBody PaymentRequestDTO requestDTO) {
        Payment payment = paymentService.initiatePayment(requestDTO);
        return ResponseEntity.ok(payment);
    }
    
    @PostMapping("/callback")
    @ApiOperation("支付回调处理")
    public ResponseEntity<Payment> processPaymentCallback(@RequestBody PaymentCallbackDTO callbackDTO) {
        Payment payment = paymentService.processPaymentCallback(callbackDTO);
        return ResponseEntity.ok(payment);
    }
    
    @PutMapping("/{paymentId}/cancel")
    @ApiOperation("取消支付")
    @PreAuthorize("hasRole('FINANCE') or hasRole('ADMIN')")
    public ResponseEntity<Payment> cancelPayment(@PathVariable Long paymentId) {
        Payment payment = paymentService.cancelPayment(paymentId);
        return ResponseEntity.ok(payment);
    }
    
    @PostMapping("/reconciliation/{bankName}")
    @ApiOperation("银行对账")
    @PreAuthorize("hasRole('FINANCE_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<List<Payment>> processReconciliation(
            @PathVariable String bankName,
            @RequestParam("file") MultipartFile file) {
        List<Payment> payments = paymentService.processReconciliation(bankName, file);
        return ResponseEntity.ok(payments);
    }
    
    @GetMapping("/statistics")
    @ApiOperation("获取支付统计")
    @PreAuthorize("hasRole('FINANCE_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<PaymentStatisticsDTO> getPaymentStatistics(DateRangeDTO dateRange) {
        PaymentStatisticsDTO statistics = paymentService.getPaymentStatistics(dateRange);
        return ResponseEntity.ok(statistics);
    }
    
    @GetMapping("/report")
    @ApiOperation("生成支付报表")
    @PreAuthorize("hasRole('FINANCE_MANAGER') or hasRole('ADMIN')")
    public ResponseEntity<byte[]> generatePaymentReport(DateRangeDTO dateRange) {
        byte[] report = paymentService.generatePaymentReport(dateRange);
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=payment_report.pdf")
                .body(report);
    }
}

Service层

java 复制代码
public interface PaymentService {
    Payment createPayment(PaymentDTO paymentDTO);
    Payment updatePayment(Long id, PaymentDTO paymentDTO);
    Payment getPaymentById(Long id);
    Payment getPaymentByNo(String paymentNo);
    Page<Payment> searchPayments(PaymentSearchDTO searchDTO, Pageable pageable);
    List<Payment> getPaymentsByPolicy(Long policyId);
    Payment initiatePayment(PaymentRequestDTO requestDTO);
    Payment processPaymentCallback(PaymentCallbackDTO callbackDTO);
    Payment cancelPayment(Long paymentId);
    List<Payment> processReconciliation(String bankName, MultipartFile file);
    PaymentStatisticsDTO getPaymentStatistics(DateRangeDTO dateRange);
    byte[] generatePaymentReport(DateRangeDTO dateRange);
}
java 复制代码
@Slf4j
@Service
@Transactional
public class PaymentServiceImpl implements PaymentService {
    
    private final PaymentRepository paymentRepository;
    private final PolicyRepository policyRepository;
    private final CommissionRepository commissionRepository;
    private final AgentRepository agentRepository;
    private final ModelMapper modelMapper;
    private final PaymentNumberGenerator paymentNumberGenerator;
    private final ReconciliationProcessor reconciliationProcessor;
    private final RabbitTemplate rabbitTemplate;
    private final AuditLogService auditLogService;
    
    public PaymentServiceImpl(PaymentRepository paymentRepository,
                            PolicyRepository policyRepository,
                            CommissionRepository commissionRepository,
                            AgentRepository agentRepository,
                            ModelMapper modelMapper,
                            PaymentNumberGenerator paymentNumberGenerator,
                            ReconciliationProcessor reconciliationProcessor,
                            RabbitTemplate rabbitTemplate,
                            AuditLogService auditLogService) {
        this.paymentRepository = paymentRepository;
        this.policyRepository = policyRepository;
        this.commissionRepository = commissionRepository;
        this.agentRepository = agentRepository;
        this.modelMapper = modelMapper;
        this.paymentNumberGenerator = paymentNumberGenerator;
        this.reconciliationProcessor = reconciliationProcessor;
        this.rabbitTemplate = rabbitTemplate;
        this.auditLogService = auditLogService;
    }

    @Override
    @CacheEvict(value = "payments", allEntries = true)
    public Payment createPayment(PaymentDTO paymentDTO) {
        Policy policy = policyRepository.findById(paymentDTO.getPolicyId())
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        // 生成支付流水号
        String paymentNo = paymentNumberGenerator.generatePaymentNo(policy.getPolicyNo(), paymentDTO.getType());
        
        // 创建支付记录
        Payment payment = modelMapper.map(paymentDTO, Payment.class);
        payment.setPaymentNo(paymentNo);
        payment.setPolicy(policy);
        payment.setCreatedAt(new Date());
        payment.setUpdatedAt(new Date());
        
        Payment savedPayment = paymentRepository.save(payment);
        
        // 如果是保费支付且状态为已完成,计算佣金
        if (paymentDTO.getType() == PaymentType.PREMIUM && 
            paymentDTO.getStatus() == PaymentStatus.COMPLETED) {
            calculateCommissions(savedPayment);
        }
        
        // 记录审计日志
        auditLogService.logPaymentCreation(savedPayment);
        
        return savedPayment;
    }

    @Override
    @CacheEvict(value = "payments", key = "#id")
    public Payment updatePayment(Long id, PaymentDTO paymentDTO) {
        Payment existingPayment = paymentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
        
        // 检查是否可以修改
        if (existingPayment.getStatus() != PaymentStatus.PENDING && 
            existingPayment.getStatus() != PaymentStatus.PROCESSING) {
            throw new BusinessException("只有待支付或处理中的支付记录可以修改");
        }
        
        modelMapper.map(paymentDTO, existingPayment);
        existingPayment.setUpdatedAt(new Date());
        
        Payment updatedPayment = paymentRepository.save(existingPayment);
        
        // 如果状态变更为已完成,计算佣金
        if (paymentDTO.getStatus() == PaymentStatus.COMPLETED && 
            updatedPayment.getType() == PaymentType.PREMIUM) {
            calculateCommissions(updatedPayment);
        }
        
        return updatedPayment;
    }

    @Override
    @Cacheable(value = "payments", key = "#id")
    public Payment getPaymentById(Long id) {
        return paymentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
    }

    @Override
    @Cacheable(value = "payments", key = "#paymentNo")
    public Payment getPaymentByNo(String paymentNo) {
        return paymentRepository.findByPaymentNo(paymentNo)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
    }

    @Override
    public Page<Payment> searchPayments(PaymentSearchDTO searchDTO, Pageable pageable) {
        return paymentRepository.searchPayments(
                searchDTO.getPaymentNo(),
                searchDTO.getPolicyNo(),
                searchDTO.getType(),
                searchDTO.getStatus(),
                searchDTO.getStartDate(),
                searchDTO.getEndDate(),
                pageable);
    }

    @Override
    public List<Payment> getPaymentsByPolicy(Long policyId) {
        return paymentRepository.findByPolicyId(policyId);
    }

    @Override
    @Transactional
    public Payment initiatePayment(PaymentRequestDTO requestDTO) {
        Policy policy = policyRepository.findById(requestDTO.getPolicyId())
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        // 生成支付流水号
        String paymentNo = paymentNumberGenerator.generatePaymentNo(policy.getPolicyNo(), PaymentType.PREMIUM);
        
        // 创建支付记录
        Payment payment = new Payment();
        payment.setPaymentNo(paymentNo);
        payment.setPolicy(policy);
        payment.setType(PaymentType.PREMIUM);
        payment.setStatus(PaymentStatus.PENDING);
        payment.setAmount(requestDTO.getAmount());
        payment.setPaymentDate(new Date());
        payment.setPaymentMethod(requestDTO.getPaymentMethod());
        payment.setCreatedAt(new Date());
        payment.setUpdatedAt(new Date());
        
        Payment savedPayment = paymentRepository.save(payment);
        
        // 调用支付网关
        initiatePaymentGateway(savedPayment);
        
        // 记录审计日志
        auditLogService.logPaymentInitiation(savedPayment);
        
        return savedPayment;
    }

    @Override
    @CacheEvict(value = "payments", key = "#callbackDTO.paymentNo")
    public Payment processPaymentCallback(PaymentCallbackDTO callbackDTO) {
        Payment payment = paymentRepository.findByPaymentNo(callbackDTO.getPaymentNo())
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
        
        payment.setStatus(callbackDTO.getStatus());
        payment.setTransactionId(callbackDTO.getTransactionId());
        payment.setRemark(callbackDTO.getRemark());
        payment.setUpdatedAt(new Date());
        
        if (callbackDTO.getStatus() == PaymentStatus.COMPLETED) {
            payment.setPaymentDate(new Date());
            
            // 如果是保费支付,计算佣金
            if (payment.getType() == PaymentType.PREMIUM) {
                calculateCommissions(payment);
            }
            
            // 更新保单状态
            updatePolicyAfterPayment(payment);
        }
        
        Payment updatedPayment = paymentRepository.save(payment);
        
        // 记录审计日志
        auditLogService.logPaymentCompletion(updatedPayment);
        
        return updatedPayment;
    }

    @Override
    @CacheEvict(value = "payments", key = "#paymentId")
    public Payment cancelPayment(Long paymentId) {
        Payment payment = paymentRepository.findById(paymentId)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
        
        // 检查是否可以取消
        if (payment.getStatus() != PaymentStatus.PENDING && 
            payment.getStatus() != PaymentStatus.PROCESSING) {
            throw new BusinessException("只有待支付或处理中的支付记录可以取消");
        }
        
        payment.setStatus(PaymentStatus.CANCELLED);
        payment.setUpdatedAt(new Date());
        
        Payment updatedPayment = paymentRepository.save(payment);
        
        // 取消关联的佣金
        cancelCommissions(updatedPayment);
        
        // 记录审计日志
        auditLogService.logPaymentCancellation(updatedPayment);
        
        return updatedPayment;
    }

    @Override
    public List<Payment> processReconciliation(String bankName, MultipartFile file) {
        // 处理银行对账文件
        List<Payment> matchedPayments = reconciliationProcessor.processReconciliationFile(bankName, file);
        
        // 更新匹配的支付记录
        matchedPayments.forEach(payment -> {
            payment.setStatus(PaymentStatus.COMPLETED);
            payment.setUpdatedAt(new Date());
            paymentRepository.save(payment);
            
            // 如果是保费支付,计算佣金
            if (payment.getType() == PaymentType.PREMIUM) {
                calculateCommissions(payment);
            }
        });
        
        return matchedPayments;
    }

    @Override
    public PaymentStatisticsDTO getPaymentStatistics(DateRangeDTO dateRange) {
        PaymentStatisticsDTO statistics = new PaymentStatisticsDTO();
        
        // 获取保费收入总额
        BigDecimal premiumIncome = paymentRepository.sumPremiumPaymentsBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setPremiumIncome(premiumIncome != null ? premiumIncome : BigDecimal.ZERO);
        
        // 获取理赔支出总额
        BigDecimal claimPayments = paymentRepository.sumClaimPaymentsBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setClaimPayments(claimPayments != null ? claimPayments : BigDecimal.ZERO);
        
        // 获取佣金支出总额
        BigDecimal commissionPayments = commissionRepository.sumPaidCommissionsBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setCommissionPayments(commissionPayments != null ? commissionPayments : BigDecimal.ZERO);
        
        return statistics;
    }

    @Override
    public byte[] generatePaymentReport(DateRangeDTO dateRange) {
        // 实际项目中应使用JasperReports等工具生成PDF报表
        // 这里简化为返回空数组
        return new byte[0];
    }
    
    private void calculateCommissions(Payment payment) {
        Policy policy = payment.getPolicy();
        
        // 获取保单的代理人
        Agent agent = policy.getAgent();
        if (agent == null) {
            return;
        }
        
        // 计算佣金 (简化版示例:固定佣金比例)
        BigDecimal commissionRate = BigDecimal.valueOf(0.15); // 15%佣金
        BigDecimal commissionAmount = payment.getAmount().multiply(commissionRate);
        
        // 创建佣金记录
        Commission commission = new Commission();
        commission.setPayment(payment);
        commission.setAgent(agent);
        commission.setAmount(commissionAmount);
        commission.setRate(commissionRate);
        commission.setCalculatedDate(new Date());
        commission.setStatus(CommissionStatus.PENDING);
        commission.setCreatedAt(new Date());
        
        commissionRepository.save(commission);
        
        // 发送佣金支付通知
        rabbitTemplate.convertAndSend("commission.payment.queue", commission.getId());
    }
    
    private void cancelCommissions(Payment payment) {
        List<Commission> commissions = commissionRepository.findByPaymentId(payment.getId());
        
        commissions.forEach(commission -> {
            commission.setStatus(CommissionStatus.CANCELLED);
            commissionRepository.save(commission);
        });
    }
    
    private void updatePolicyAfterPayment(Payment payment) {
        Policy policy = payment.getPolicy();
        
        if (payment.getType() == PaymentType.PREMIUM) {
            // 保费支付成功后更新保单状态
            if (policy.getStatus() == PolicyStatus.PENDING_PAYMENT) {
                policy.setStatus(PolicyStatus.ACTIVE);
                policyRepository.save(policy);
                
                // 发送保单激活通知
                rabbitTemplate.convertAndSend("policy.activated.queue", policy.getId());
            }
        }
    }
    
    private void initiatePaymentGateway(Payment payment) {
        // 这里应该有调用实际支付网关的逻辑
        // 简化版示例:发送到消息队列异步处理
        rabbitTemplate.convertAndSend("payment.process.queue", payment.getId());
    }
    
    @Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
    public void processDailyReconciliation() {
        // 自动下载银行对账文件并处理
        log.info("开始执行每日自动对账...");
        // 实现逻辑...
    }
}

Repository层

java 复制代码
@Slf4j
@Service
@Transactional
public class PaymentServiceImpl implements PaymentService {
    
    private final PaymentRepository paymentRepository;
    private final PolicyRepository policyRepository;
    private final CommissionRepository commissionRepository;
    private final AgentRepository agentRepository;
    private final ModelMapper modelMapper;
    private final PaymentNumberGenerator paymentNumberGenerator;
    private final ReconciliationProcessor reconciliationProcessor;
    private final RabbitTemplate rabbitTemplate;
    private final AuditLogService auditLogService;
    
    public PaymentServiceImpl(PaymentRepository paymentRepository,
                            PolicyRepository policyRepository,
                            CommissionRepository commissionRepository,
                            AgentRepository agentRepository,
                            ModelMapper modelMapper,
                            PaymentNumberGenerator paymentNumberGenerator,
                            ReconciliationProcessor reconciliationProcessor,
                            RabbitTemplate rabbitTemplate,
                            AuditLogService auditLogService) {
        this.paymentRepository = paymentRepository;
        this.policyRepository = policyRepository;
        this.commissionRepository = commissionRepository;
        this.agentRepository = agentRepository;
        this.modelMapper = modelMapper;
        this.paymentNumberGenerator = paymentNumberGenerator;
        this.reconciliationProcessor = reconciliationProcessor;
        this.rabbitTemplate = rabbitTemplate;
        this.auditLogService = auditLogService;
    }

    @Override
    @CacheEvict(value = "payments", allEntries = true)
    public Payment createPayment(PaymentDTO paymentDTO) {
        Policy policy = policyRepository.findById(paymentDTO.getPolicyId())
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        // 生成支付流水号
        String paymentNo = paymentNumberGenerator.generatePaymentNo(policy.getPolicyNo(), paymentDTO.getType());
        
        // 创建支付记录
        Payment payment = modelMapper.map(paymentDTO, Payment.class);
        payment.setPaymentNo(paymentNo);
        payment.setPolicy(policy);
        payment.setCreatedAt(new Date());
        payment.setUpdatedAt(new Date());
        
        Payment savedPayment = paymentRepository.save(payment);
        
        // 如果是保费支付且状态为已完成,计算佣金
        if (paymentDTO.getType() == PaymentType.PREMIUM && 
            paymentDTO.getStatus() == PaymentStatus.COMPLETED) {
            calculateCommissions(savedPayment);
        }
        
        // 记录审计日志
        auditLogService.logPaymentCreation(savedPayment);
        
        return savedPayment;
    }

    @Override
    @CacheEvict(value = "payments", key = "#id")
    public Payment updatePayment(Long id, PaymentDTO paymentDTO) {
        Payment existingPayment = paymentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
        
        // 检查是否可以修改
        if (existingPayment.getStatus() != PaymentStatus.PENDING && 
            existingPayment.getStatus() != PaymentStatus.PROCESSING) {
            throw new BusinessException("只有待支付或处理中的支付记录可以修改");
        }
        
        modelMapper.map(paymentDTO, existingPayment);
        existingPayment.setUpdatedAt(new Date());
        
        Payment updatedPayment = paymentRepository.save(existingPayment);
        
        // 如果状态变更为已完成,计算佣金
        if (paymentDTO.getStatus() == PaymentStatus.COMPLETED && 
            updatedPayment.getType() == PaymentType.PREMIUM) {
            calculateCommissions(updatedPayment);
        }
        
        return updatedPayment;
    }

    @Override
    @Cacheable(value = "payments", key = "#id")
    public Payment getPaymentById(Long id) {
        return paymentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
    }

    @Override
    @Cacheable(value = "payments", key = "#paymentNo")
    public Payment getPaymentByNo(String paymentNo) {
        return paymentRepository.findByPaymentNo(paymentNo)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
    }

    @Override
    public Page<Payment> searchPayments(PaymentSearchDTO searchDTO, Pageable pageable) {
        return paymentRepository.searchPayments(
                searchDTO.getPaymentNo(),
                searchDTO.getPolicyNo(),
                searchDTO.getType(),
                searchDTO.getStatus(),
                searchDTO.getStartDate(),
                searchDTO.getEndDate(),
                pageable);
    }

    @Override
    public List<Payment> getPaymentsByPolicy(Long policyId) {
        return paymentRepository.findByPolicyId(policyId);
    }

    @Override
    @Transactional
    public Payment initiatePayment(PaymentRequestDTO requestDTO) {
        Policy policy = policyRepository.findById(requestDTO.getPolicyId())
                .orElseThrow(() -> new ResourceNotFoundException("保单不存在"));
        
        // 生成支付流水号
        String paymentNo = paymentNumberGenerator.generatePaymentNo(policy.getPolicyNo(), PaymentType.PREMIUM);
        
        // 创建支付记录
        Payment payment = new Payment();
        payment.setPaymentNo(paymentNo);
        payment.setPolicy(policy);
        payment.setType(PaymentType.PREMIUM);
        payment.setStatus(PaymentStatus.PENDING);
        payment.setAmount(requestDTO.getAmount());
        payment.setPaymentDate(new Date());
        payment.setPaymentMethod(requestDTO.getPaymentMethod());
        payment.setCreatedAt(new Date());
        payment.setUpdatedAt(new Date());
        
        Payment savedPayment = paymentRepository.save(payment);
        
        // 调用支付网关
        initiatePaymentGateway(savedPayment);
        
        // 记录审计日志
        auditLogService.logPaymentInitiation(savedPayment);
        
        return savedPayment;
    }

    @Override
    @CacheEvict(value = "payments", key = "#callbackDTO.paymentNo")
    public Payment processPaymentCallback(PaymentCallbackDTO callbackDTO) {
        Payment payment = paymentRepository.findByPaymentNo(callbackDTO.getPaymentNo())
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
        
        payment.setStatus(callbackDTO.getStatus());
        payment.setTransactionId(callbackDTO.getTransactionId());
        payment.setRemark(callbackDTO.getRemark());
        payment.setUpdatedAt(new Date());
        
        if (callbackDTO.getStatus() == PaymentStatus.COMPLETED) {
            payment.setPaymentDate(new Date());
            
            // 如果是保费支付,计算佣金
            if (payment.getType() == PaymentType.PREMIUM) {
                calculateCommissions(payment);
            }
            
            // 更新保单状态
            updatePolicyAfterPayment(payment);
        }
        
        Payment updatedPayment = paymentRepository.save(payment);
        
        // 记录审计日志
        auditLogService.logPaymentCompletion(updatedPayment);
        
        return updatedPayment;
    }

    @Override
    @CacheEvict(value = "payments", key = "#paymentId")
    public Payment cancelPayment(Long paymentId) {
        Payment payment = paymentRepository.findById(paymentId)
                .orElseThrow(() -> new ResourceNotFoundException("支付记录不存在"));
        
        // 检查是否可以取消
        if (payment.getStatus() != PaymentStatus.PENDING && 
            payment.getStatus() != PaymentStatus.PROCESSING) {
            throw new BusinessException("只有待支付或处理中的支付记录可以取消");
        }
        
        payment.setStatus(PaymentStatus.CANCELLED);
        payment.setUpdatedAt(new Date());
        
        Payment updatedPayment = paymentRepository.save(payment);
        
        // 取消关联的佣金
        cancelCommissions(updatedPayment);
        
        // 记录审计日志
        auditLogService.logPaymentCancellation(updatedPayment);
        
        return updatedPayment;
    }

    @Override
    public List<Payment> processReconciliation(String bankName, MultipartFile file) {
        // 处理银行对账文件
        List<Payment> matchedPayments = reconciliationProcessor.processReconciliationFile(bankName, file);
        
        // 更新匹配的支付记录
        matchedPayments.forEach(payment -> {
            payment.setStatus(PaymentStatus.COMPLETED);
            payment.setUpdatedAt(new Date());
            paymentRepository.save(payment);
            
            // 如果是保费支付,计算佣金
            if (payment.getType() == PaymentType.PREMIUM) {
                calculateCommissions(payment);
            }
        });
        
        return matchedPayments;
    }

    @Override
    public PaymentStatisticsDTO getPaymentStatistics(DateRangeDTO dateRange) {
        PaymentStatisticsDTO statistics = new PaymentStatisticsDTO();
        
        // 获取保费收入总额
        BigDecimal premiumIncome = paymentRepository.sumPremiumPaymentsBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setPremiumIncome(premiumIncome != null ? premiumIncome : BigDecimal.ZERO);
        
        // 获取理赔支出总额
        BigDecimal claimPayments = paymentRepository.sumClaimPaymentsBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setClaimPayments(claimPayments != null ? claimPayments : BigDecimal.ZERO);
        
        // 获取佣金支出总额
        BigDecimal commissionPayments = commissionRepository.sumPaidCommissionsBetween(
                dateRange.getStartDate(), dateRange.getEndDate());
        statistics.setCommissionPayments(commissionPayments != null ? commissionPayments : BigDecimal.ZERO);
        
        return statistics;
    }

    @Override
    public byte[] generatePaymentReport(DateRangeDTO dateRange) {
        // 实际项目中应使用JasperReports等工具生成PDF报表
        // 这里简化为返回空数组
        return new byte[0];
    }
    
    private void calculateCommissions(Payment payment) {
        Policy policy = payment.getPolicy();
        
        // 获取保单的代理人
        Agent agent = policy.getAgent();
        if (agent == null) {
            return;
        }
        
        // 计算佣金 (简化版示例:固定佣金比例)
        BigDecimal commissionRate = BigDecimal.valueOf(0.15); // 15%佣金
        BigDecimal commissionAmount = payment.getAmount().multiply(commissionRate);
        
        // 创建佣金记录
        Commission commission = new Commission();
        commission.setPayment(payment);
        commission.setAgent(agent);
        commission.setAmount(commissionAmount);
        commission.setRate(commissionRate);
        commission.setCalculatedDate(new Date());
        commission.setStatus(CommissionStatus.PENDING);
        commission.setCreatedAt(new Date());
        
        commissionRepository.save(commission);
        
        // 发送佣金支付通知
        rabbitTemplate.convertAndSend("commission.payment.queue", commission.getId());
    }
    
    private void cancelCommissions(Payment payment) {
        List<Commission> commissions = commissionRepository.findByPaymentId(payment.getId());
        
        commissions.forEach(commission -> {
            commission.setStatus(CommissionStatus.CANCELLED);
            commissionRepository.save(commission);
        });
    }
    
    private void updatePolicyAfterPayment(Payment payment) {
        Policy policy = payment.getPolicy();
        
        if (payment.getType() == PaymentType.PREMIUM) {
            // 保费支付成功后更新保单状态
            if (policy.getStatus() == PolicyStatus.PENDING_PAYMENT) {
                policy.setStatus(PolicyStatus.ACTIVE);
                policyRepository.save(policy);
                
                // 发送保单激活通知
                rabbitTemplate.convertAndSend("policy.activated.queue", policy.getId());
            }
        }
    }
    
    private void initiatePaymentGateway(Payment payment) {
        // 这里应该有调用实际支付网关的逻辑
        // 简化版示例:发送到消息队列异步处理
        rabbitTemplate.convertAndSend("payment.process.queue", payment.getId());
    }
    
    @Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
    public void processDailyReconciliation() {
        // 自动下载银行对账文件并处理
        log.info("开始执行每日自动对账...");
        // 实现逻辑...
    }
}
java 复制代码
public interface CommissionRepository extends JpaRepository<Commission, Long> {
    
    List<Commission> findByPaymentId(Long paymentId);
    
    List<Commission> findByAgentId(Long agentId);
    
    List<Commission> findByStatus(CommissionStatus status);
    
    @Query("SELECT SUM(c.amount) FROM Commission c WHERE c.status = 'PAID' AND c.paidDate BETWEEN :start AND :end")
    BigDecimal sumPaidCommissionsBetween(@Param("start") Date start, @Param("end") Date end);
}