news 2026/10/21 2:47:17

Spring Boot保险理赔系统:全链路业务闭环与可扩展架构实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot保险理赔系统:全链路业务闭环与可扩展架构实践

简介:MF00901-Java保险理赔系统源码是一套面向Java初/中级开发者的企业级项目实战资源,聚焦保险行业核心业务场景,完整覆盖理赔申请、审核、赔付等全流程功能实现,适用于学习Spring Boot+MyBatis全栈开发、MVC架构落地及金融类系统设计。压缩包共1729个文件,含45个核心Java类、192个HTML页面、140个CSS样式、316个JS脚本、222个PNG与242个GIF资源图,以及SQL建表脚本、RESTful接口定义JSON、Spring配置XML和Log4j日志配置等关键工程文件,整体75.19MB,结构规范,模块划分清晰。目前已有158人下载学习,可直接导入IDE运行调试,配套CHANGE-LOG说明迭代演进,readme提供部署指引,war包支持一键发布,适合用于课程设计、毕业项目或企业级Java开发能力进阶训练。

1. 一个跑在 Spring Boot 上的保险理赔业务闭环:从报案、定损、核赔到支付,全链路可调试、可替换、可扩展

上周帮某高校实验室做课程设计支撑时,带学生跑通了这个保险理赔系统源码包——不是 Demo,不是单页表单,而是真实覆盖「客户报案 → 理赔员录入 → 查勘定损 → 核赔审批 → 财务支付 → 结案归档」六阶段的完整业务流。它没用微服务拆分,但通过清晰的领域分层(domain / application / infrastructure)把「车险小额快赔」和「健康险长期理算」两种模式都揉进了同一套模型里;数据库用 MySQL 8.0,但所有 SQL 都封装在 MyBatis-Plus 的LambdaQueryWrapper里,连@Select注解都几乎见不到;最让我意外的是,它的「理算规则引擎」没上 Drools,而是用策略模式 + Spring Bean 名称动态加载实现的——改一条赔付比例,不用重启,只改配置文件里的rule.strategy=health-accident-v2就能切过去。如果你正要交毕设、接外包、或者想拿个有业务纵深的 Spring Boot 项目练手,这个源码包比“图书管理系统”“学生成绩平台”实在得多:它有真实字段(如injury_level: 1-10 级伤残)、真实约束(如「同一保单 30 天内仅允许一次小额快赔」)、真实对接点(模拟了与第三方查勘 App 的 WebSocket 推送)。别被标题里的“MF00901”编号吓住,这是一份能让你在面试时指着代码说“我当时调过这个核赔状态机”的实战资源。

2. 项目结构与技术栈:Spring Boot 2.7.x + MyBatis-Plus 3.5.x + Vue 2.6.x 前后端分离架构

这个项目不是单体巨石,而是标准的前后端分离结构,但所有模块都打包在一个 ZIP 里,解压即得完整工程。我习惯先看pom.xml和package.json锁版本,再扫目录树——这是避免“跑不起来第一关”的血泪经验。下面我把整个结构按开发视角重新组织,标出哪些是必须动的、哪些是可跳过的、哪些藏着玄机。

2.1 后端核心模块:insurance-core模块是业务中枢,insurance-api是网关入口

整个后端基于 Maven 多模块构建,主模块名为insurance-parent,其下包含:

  • insurance-core:真正干活的模块。所有领域实体(ClaimApplication、LossAssessment、CompensationCalculation)、应用服务(ClaimApplicationService、AssessmentService)、仓储接口(ClaimApplicationRepository)都在这里。注意:它的src/main/resources/mapper/下没有 XML 文件,所有 SQL 都写在@Mapper接口的方法上,比如ClaimApplicationMapper.java里有:

    @Select("SELECT * FROM t_claim_application WHERE policy_no = #{policyNo} AND status IN (#{statusList}) ORDER BY create_time DESC") List<ClaimApplication> findByPolicyNoAndStatus(@Param("policyNo") String policyNo, @Param("statusList") List<Integer> statusList);

    提示:MyBatis-Plus 的QueryWrapper在这里被刻意规避了,原因是理赔查询常需多表关联(如关联保单表、被保人表、查勘记录表),而QueryWrapper动态拼 JOIN 容易出错。作者选择手写 SQL +@SelectProvider方式,更可控。

  • insurance-api:Spring Boot Web 入口模块,只含 Controller 和全局异常处理。所有接口路径以/api/v1/claims开头,符合 RESTful 规范。关键点在于它的WebMvcConfigurer配置了自定义的ClaimRequestBodyAdvice,对所有POST /api/v1/claims/submit请求自动校验报案人手机号格式、身份证号合法性(用正则 + Luhn 算法),并拦截重复提交(基于X-Request-IDHeader 做幂等)。

  • insurance-infrastructure:基础设施层,含数据库连接池(HikariCP)、Redis 缓存配置(用于存储临时查勘图片 URL)、邮件发送模板(理赔进度通知)、以及最重要的——规则引擎注册中心。它的RuleEngineConfiguration.java里用@Bean注册了多个CompensationRuleStrategy实现类,并通过@ConditionalOnProperty(name = "rule.strategy", havingValue = "car-small-loss")控制加载哪个策略。

2.2 前端结构:Vue 2.6 + Element UI,路由按理赔生命周期组织

前端位于src/main/webapp/目录下(注意:不是 Vue CLI 默认的src/),用的是传统webpack.config.js构建。整个 SPA 的路由设计直击业务本质:

  • /claims/apply:报案录入页,表单字段多达 28 项(含事故现场照片上传、GPS 坐标、第三方责任方信息),使用el-upload组件,上传逻辑封装在upload-service.js中,支持断点续传(基于文件 MD5 分片);
  • /claims/assessment:查勘定损页,核心是AssessmentForm.vue,其中damageItems是动态表格,每行可添加「损伤部位」「损伤程度」「维修方式」「预估费用」,所有费用计算实时触发calcTotalAmount()方法;
  • /claims/approval:核赔审批页,采用流程图可视化组件(vue-flow-chart),显示当前节点(如「初审」「复核」「终审」)、审批人、处理时限(倒计时),点击「同意」会调用/api/v1/claims/{id}/approve并携带nextNode参数;
  • /claims/payment:支付确认页,展示银行账户脱敏信息(6228 **** **** 1234),调用/api/v1/payments/initiate发起模拟支付,返回paymentId后轮询/api/v1/payments/{id}/status获取结果。

注意:所有 API 请求都经过request.js封装,自动注入Authorization: Bearer {token},而 token 来自登录后存入localStorage的insurance_token。没有用 Vuex 做全局状态管理,而是每个页面组件自己维护data(),这对理解业务数据流反而更友好。

2.3 数据库设计:MySQL 8.0,5 张核心表撑起全链路

schema.sql文件在src/main/resources/下,共 12 张表,但真正承载业务的是以下 5 张:

表名主要字段业务作用关键约束
t_policypolicy_no,insured_id,product_code,effective_date,expiry_date保单主表UNIQUE(policy_no),CHECK(expiry_date > effective_date)
t_claim_applicationclaim_no,policy_no,applicant_name,mobile,accident_date,status报案申请表status ENUM('DRAFT','SUBMITTED','ASSESSING','APPROVING','PAID','CLOSED')
t_loss_assessmentassessment_id,claim_no,assessor_id,damage_items_json,total_amount查勘定损表damage_items_json TEXT存 JSON 数组,含部位、程度、费用明细
t_compensation_calculationcalc_id,claim_no,rule_version,base_amount,deductible,final_amount理算结果表rule_version VARCHAR(20)记录所用规则版本,用于审计追溯
t_payment_recordpayment_id,claim_no,bank_account_masked,amount,status,pay_time支付记录表status ENUM('INITIATED','SUCCESS','FAILED','REFUNDED')

特别说明:damage_items_json字段虽为 TEXT,但后端LossAssessment实体类中用@TableField(typeHandler = JacksonTypeHandler.class)映射为List<DamageItem>,序列化/反序列化由 Jackson 自动完成,避免了手动解析 JSON 字符串的繁琐。

3. 运行前必做三件事:JDK 版本、数据库初始化、前端依赖安装

别急着mvn spring-boot:run—— 这个项目对环境有明确要求,跳过任一环节都会卡在启动阶段。我整理了一份“三步通关清单”,按顺序执行,基本能绕开 90% 的启动失败。

3.1 JDK 必须为 11,且需设置 JAVA_HOME 指向 JDK 11 根目录

pom.xml中<java.version>明确写死为11,且insurance-core模块用了var关键字声明局部变量(如var claim = claimRepository.findById(id)),这是 JDK 11+ 特性。若你本地是 JDK 17 或 JDK 8,启动时会报Unsupported class file major version 61或Cannot resolve symbol 'var'。解决方案:

  • 下载 OpenJDK 11(推荐 Adoptium Temurin 版本);
  • 解压后设置系统环境变量:
    # Linux/macOS export JAVA_HOME=/path/to/jdk-11.0.21 export PATH=$JAVA_HOME/bin:$PATH
    # Windows PowerShell $env:JAVA_HOME="C:\Program Files\Java\jdk-11.0.21" $env:Path="$env:JAVA_HOME\bin;$env:Path"
  • 验证:java -version输出必须为openjdk version "11.0.21"。

提示:IDEA 用户需在File → Project Structure → Project中将 Project SDK 和 Project language level 都设为 11;Eclipse 用户需右键项目 →Properties → Java Build Path → Libraries,移除旧 JRE,添加 JDK 11。

3.2 MySQL 8.0 初始化:创建库、执行建表、导入初始数据

项目未提供一键初始化脚本,需手动操作:

  1. 创建数据库(字符集必须为utf8mb4,否则中文存入会乱码):

    CREATE DATABASE insurance_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. 执行src/main/resources/schema.sql建表(注意:该文件末尾有INSERT INTO t_user插入默认管理员账号,账号admin,密码123456,首次登录后请立即修改);

  3. 修改application.yml中数据库配置:

    spring: datasource: url: jdbc:mysql://localhost:3306/insurance_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_mysql_root_password

    注意:MySQL 8.0 默认启用caching_sha2_password认证插件,若连接报Public Key Retrieval is not allowed,需在 URL 中加allowPublicKeyRetrieval=true,或在 MySQL 中执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';切换认证方式。

3.3 前端构建:npm install 后需 patch 一个 Element UI 的小 bug

前端位于src/main/webapp/,需单独构建:

cd src/main/webapp npm install # 此处必须执行 patch,否则登录页 el-input 组件无法聚焦 npm run patch-element-fix npm run build

patch-element-fix是项目自带的脚本,作用是修改node_modules/element-ui/lib/input.js中一处v-model绑定逻辑(Vue 2.6 对input事件监听的兼容性问题)。若跳过此步,登录时输入框点击无反应,控制台报TypeError: Cannot read property 'focus' of undefined。

构建成功后,静态资源会输出到src/main/resources/static/,Spring Boot 启动时自动托管。

4. 常见问题排查:启动失败、登录 401、查勘图片不显示、核赔状态不更新

跑通一个业务系统,80% 的时间花在解决“看似简单实则坑深”的问题上。我把带学生调试时踩过的典型问题整理成四条,每条按「现象 → 原因 → 解决」给出可立即执行的方案,拒绝模糊描述。

4.1 现象:Spring Boot 启动报Failed to configure a DataSource,提示url为空

原因:application.yml中spring.datasource.url配置项被注释了,或 YAML 缩进错误(YAML 对空格敏感,url:前多了空格会导致解析失败)。
解决:打开src/main/resources/application.yml,找到spring:下的datasource:区块,确认url:行开头无空格,且值不为空字符串;用在线 YAML 校验器(如 https://yamlchecker.com/ )粘贴全文验证格式。

4.2 现象:前端登录页输入admin/123456后,Network 面板显示POST /api/v1/auth/login返回 401

原因:后端InsuranceAuthController.java的login()方法中,密码校验逻辑调用了passwordEncoder.matches(rawPassword, encodedPassword),而application.yml中security.password-encoder配置为bcrypt,但数据库t_user表中password字段存的是明文123456(schema.sql初始化数据未加密)。
解决:方案一(推荐):修改schema.sql,将INSERT INTO t_user的密码字段改为 BCrypt 加密值,如$2a$10$KQVZzLqRfGxWYjNkOoPmQeUuFvGhIjKlMnOpQrStUvWxYzAbCdEfG(可用在线工具生成);方案二(临时):在SecurityConfig.java中将passwordEncoderBean 替换为NoOpPasswordEncoder.getInstance(),仅用于本地调试。

4.3 现象:查勘页上传图片后,预览区显示空白,Network 面板看到GET /api/v1/files/{fileId}返回 404

原因:文件存储路径配置错误。后端FileStorageService.java中getFilePath(String fileId)方法拼接路径时,依赖file.storage.base-path配置项,默认值为./uploads/,但该目录未被创建,或权限不足。
解决:在项目根目录手动创建uploads文件夹,并确保运行 Java 进程的用户对该目录有读写权限;同时检查application.yml中:

file: storage: base-path: ./uploads/

确保路径末尾有/,且无中文或特殊字符。

4.4 现象:核赔审批页点击「同意」后,页面状态未变,数据库t_claim_application.status仍为ASSESSING

原因:审批流程的「状态机」逻辑在ClaimApplicationService.approve()方法中,它调用stateMachine.sendEvent(Mono.just(MessageBuilder.withPayload(new ClaimEvent(ClaimEventType.APPROVE)).setHeader("claimId", claimId).build())),但StateMachineConfiguration.java中未正确配置StateContext的transition,导致事件未被消费。
解决:打开StateMachineConfiguration.java,找到@Override public void configure(StateMachineConfigurationConfigurer<ClaimState, ClaimEventType> config)方法,在config.withConfiguration()块内,确认已添加:

.configureConfiguration() .withConfiguration() .autoStartup(true) // 必须设为 true,否则状态机不启动 .and() .withConfiguration() .machineRuntime(machineRuntime())

若autoStartup(false),则需手动调用stateMachine.start()。

5. 核心业务逻辑拆解:从报案提交到支付成功的 7 个关键方法链

理解一个业务系统,不能只看接口和页面,得顺着一条主干请求,把后端代码逐层剥开。我选了最典型的「客户报案 → 理赔员审核通过 → 财务支付」链路,从ClaimApplicationController.submit()开始,带你走完全部 7 个关键方法,每一步都标出参数含义、返回值作用、以及为什么这么设计。

5.1ClaimApplicationController.submit():接收 JSON 报案数据,做基础校验与幂等控制

@PostMapping("/submit") public ResponseEntity<ApiResponse<ClaimApplication>> submit( @Valid @RequestBody ClaimApplicationSubmitDTO dto, HttpServletRequest request) { String requestId = request.getHeader("X-Request-ID"); if (StringUtils.isBlank(requestId)) { requestId = UUID.randomUUID().toString(); } // 幂等控制:检查是否已存在相同 requestId 的报案 if (claimApplicationService.existsByRequestId(requestId)) { return ResponseEntity.ok(ApiResponse.success( claimApplicationService.findByRequestId(requestId))); } ClaimApplication application = claimApplicationService.createFromDTO(dto, requestId); return ResponseEntity.ok(ApiResponse.success(application)); }

逻辑说明:

  • @Valid触发 DTO 的@NotBlank、@Pattern等注解校验(如mobile必须匹配^1[3-9]\\d{9}$);
  • X-Request-ID是前端生成的唯一标识,用于防止网络重试导致重复报案;
  • existsByRequestId()查询t_claim_application.request_id字段,该字段有唯一索引;
  • createFromDTO()是真正创建报案对象的方法,它还会生成claim_no(格式为CLM-YYYYMMDD-XXXXXX)并设置初始状态DRAFT。

5.2ClaimApplicationService.createFromDTO():组装领域对象,调用仓储保存

@Transactional public ClaimApplication createFromDTO(ClaimApplicationSubmitDTO dto, String requestId) { ClaimApplication application = new ClaimApplication(); application.setClaimNo(generateClaimNo()); // CLM-20240520-000001 application.setPolicyNo(dto.getPolicyNo()); application.setApplicantName(dto.getApplicantName()); application.setMobile(dto.getMobile()); application.setAccidentDate(dto.getAccidentDate()); application.setRequestId(requestId); application.setStatus(ClaimStatus.DRAFT); // 初始状态 // 关联保单校验:检查保单是否存在、是否在有效期内 Policy policy = policyRepository.findByPolicyNo(dto.getPolicyNo()); if (policy == null || !policy.isValidOn(application.getAccidentDate())) { throw new BusinessException("保单不存在或已过期"); } // 保存报案 return claimApplicationRepository.save(application); }

参数说明:

  • dto:前端传来的ClaimApplicationSubmitDTO,含报案人信息、事故时间、地点、简述;
  • requestId:幂等 ID,存入数据库用于去重;
  • policy.isValidOn()是Policy实体的业务方法,内部比较accidentDate是否在effectiveDate和expiryDate之间。

5.3AssessmentService.startAssessment():查勘任务创建,触发 WebSocket 推送

@Transactional public AssessmentTask startAssessment(Long claimId, Long assessorId) { ClaimApplication application = claimApplicationRepository.findById(claimId) .orElseThrow(() -> new BusinessException("报案不存在")); if (!application.getStatus().equals(ClaimStatus.SUBMITTED)) { throw new BusinessException("报案状态非 SUBMITTED,不可发起查勘"); } AssessmentTask task = new AssessmentTask(); task.setClaimId(claimId); task.setAssessorId(assessorId); task.setStatus(AssessmentStatus.PENDING); task.setCreateTime(LocalDateTime.now()); assessmentTaskRepository.save(task); // 推送查勘任务给指定查勘员的 App(模拟) webSocketService.sendMessageToUser(assessorId, new AssessmentNotice(task.getClaimId(), "新查勘任务已分配")); // 更新报案状态为 ASSESSING application.setStatus(ClaimStatus.ASSESSING); claimApplicationRepository.save(application); return task; }

关键点:

  • 状态守卫(Guard Clause):只允许从SUBMITTED状态进入ASSESSING,违反则抛业务异常;
  • webSocketService.sendMessageToUser()是模拟推送,实际项目中会对接 MQTT 或 Kafka;
  • 事务内完成查勘任务创建 + 报案状态更新,保证数据一致性。

5.4CompensationCalculationService.calculate():调用规则引擎,生成理算结果

public CompensationCalculation calculate(Long claimId) { ClaimApplication application = claimApplicationRepository.findById(claimId).get(); LossAssessment assessment = lossAssessmentRepository.findByClaimId(claimId); // 根据保单产品类型(car / health)和事故类型(small-loss / major-injury)选择规则 String ruleKey = determineRuleKey(application.getProductCode(), assessment.getAccidentType()); // 从 Spring 容器获取对应策略 Bean CompensationRuleStrategy strategy = applicationContext.getBean(ruleKey, CompensationRuleStrategy.class); CompensationCalculation calculation = strategy.calculate(application, assessment); calculation.setClaimId(claimId); calculation.setRuleVersion(ruleKey); calculation.setCreateTime(LocalDateTime.now()); return compensationCalculationRepository.save(calculation); } private String determineRuleKey(String productCode, String accidentType) { return productCode + "-" + accidentType; // e.g., "car-small-loss" }

设计亮点:

  • determineRuleKey()生成 Bean 名称,如car-small-loss,对应@Component("car-small-loss")的策略类;
  • applicationContext.getBean()动态获取,无需 if-else 判断,新增规则只需加一个@Component类;
  • ruleVersion字段存入数据库,为后续审计提供依据。

5.5ClaimApplicationService.approve():核赔审批,驱动状态机流转

@Transactional public void approve(Long claimId, String approverId, String comment) { StateMachine<ClaimState, ClaimEventType> stateMachine = stateMachineFactory.getStateMachine(); // 构建状态机事件 Message<ClaimEventType> message = MessageBuilder .withPayload(ClaimEventType.APPROVE) .setHeader("claimId", claimId) .setHeader("approverId", approverId) .setHeader("comment", comment) .build(); // 发送事件,状态机自动处理状态变更 stateMachine.send(Mono.just(message)).block(); }

为什么用状态机?

  • 理赔流程有严格状态约束(如不能从PAID回退到APPROVING);
  • 状态变更伴随副作用(如APPROVING → PAID时需生成支付单);
  • StateMachine的Transition可配置Action,在状态变化时自动执行sendPaymentNotification()等逻辑。

5.6PaymentService.initiatePayment():发起支付,生成支付单并调用模拟支付网关

@Transactional public PaymentRecord initiatePayment(Long claimId) { CompensationCalculation calculation = compensationCalculationRepository .findByClaimId(claimId); ClaimApplication application = claimApplicationRepository.findById(claimId).get(); PaymentRecord record = new PaymentRecord(); record.setClaimId(claimId); record.setAmount(calculation.getFinalAmount()); record.setBankAccountMasked(maskBankAccount(application.getBankAccount())); record.setStatus(PaymentStatus.INITIATED); record.setCreateTime(LocalDateTime.now()); paymentRecordRepository.save(record); // 调用模拟支付网关(实际项目中为 HTTP 调用第三方) boolean success = mockPaymentGateway.process(record.getPaymentId(), record.getAmount()); if (success) { record.setStatus(PaymentStatus.SUCCESS); record.setPayTime(LocalDateTime.now()); // 更新报案状态为 PAID application.setStatus(ClaimStatus.PAID); claimApplicationRepository.save(application); } else { record.setStatus(PaymentStatus.FAILED); } return paymentRecordRepository.save(record); }

注意:

  • mockPaymentGateway.process()是模拟方法,返回true即视为支付成功;
  • 支付成功后,同步更新t_claim_application.status为PAID,这是业务闭环的关键;
  • maskBankAccount()方法对银行卡号做脱敏(保留前 4 后 4,中间用*代替)。

5.7ClaimApplicationService.closeCase():结案归档,释放资源

@Transactional public void closeCase(Long claimId) { ClaimApplication application = claimApplicationRepository.findById(claimId).get(); if (!application.getStatus().equals(ClaimStatus.PAID)) { throw new BusinessException("仅已支付报案可结案"); } // 归档逻辑:压缩附件、生成 PDF 结案报告、移动至历史库(本项目简化为更新状态) application.setStatus(ClaimStatus.CLOSED); application.setCloseTime(LocalDateTime.now()); // 清理临时数据:删除查勘草稿、理算缓存 assessmentDraftRepository.deleteByClaimId(claimId); calculationCache.evict(claimId); claimApplicationRepository.save(application); }

结案的意义:

  • CLOSED是最终态,不可逆;
  • calculationCache.evict()是 Spring Cache 注解@Cacheable的手动清除,避免陈旧理算结果被复用;
  • 真实系统中此处会调用文档服务生成 PDF,并存入对象存储。

6. 二次开发实战:如何快速接入新险种(如“宠物医疗险”)并定制理算规则

这是我带学生做课程设计时最常被问的问题:“老师,我想加一个宠物险,怎么改?”答案不是“重写一套”,而是利用现有架构的扩展点,精准插入。下面以“宠物医疗险”为例,演示从零到上线的完整改造路径,每一步都可验证、可回滚。

6.1 第一步:扩展保单产品类型,新增pet-medical枚举值

在insurance-core模块中,找到ProductCode.java枚举类:

public enum ProductCode { CAR_SMALL_LOSS("car-small-loss", "车险小额快赔"), HEALTH_ACCIDENT("health-accident", "健康意外险"), // 新增一行 ↓ PET_MEDICAL("pet-medical", "宠物医疗险"); private final String code; private final String description; ProductCode(String code, String description) { this.code = code; this.description = description; } public static ProductCode fromCode(String code) { return Arrays.stream(values()) .filter(v -> v.code.equals(code)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("未知产品代码: " + code)); } // getter... }

验证方法:启动后访问/api/v1/products(该接口返回所有ProductCode列表),确认pet-medical出现在响应中。

6.2 第二步:定义宠物险专属报案 DTO 与领域实体

新建PetMedicalClaimDTO.java(位于insurance-core/src/main/java/com/example/insurance/dto/):

public class PetMedicalClaimDTO { @NotBlank(message = "宠物名称不能为空") private String petName; @NotBlank(message = "宠物品种不能为空") private String petBreed; @Min(value = 1, message = "宠物年龄不能小于1个月") private Integer petAgeMonth; @DecimalMin(value = "0.01", message = "医疗费用不能小于0.01元") private BigDecimal medicalCost; @NotBlank(message = "医院名称不能为空") private String hospitalName; // getters & setters... }

然后在ClaimApplicationSubmitDTO.java中增加一个petMedical字段,并用@Valid校验:

public class ClaimApplicationSubmitDTO { // ...原有字段 @Valid private PetMedicalClaimDTO petMedical; // 仅当 productCode == pet-medical 时生效 // getter/setter... }

关键技巧:在ClaimApplicationController.submit()的校验逻辑中,加入条件判断:

if (dto.getProductCode().equals(ProductCode.PET_MEDICAL.code)) { // 强制校验 petMedical 字段不为空 if (dto.getPetMedical() == null) { throw new BusinessException("宠物险报案必须填写宠物信息"); } }

6.3 第三步:编写宠物险理算策略,实现CompensationRuleStrategy接口

新建PetMedicalCompensationRule.java(位于insurance-core/src/main/java/com/example/insurance/rule/):

@Component("pet-medical") public class PetMedicalCompensationRule implements CompensationRuleStrategy { @Override public CompensationCalculation calculate(ClaimApplication application, LossAssessment assessment) { PetMedicalClaimDTO petDto = application.getPetMedical(); // 从报案中提取 BigDecimal baseAmount = petDto.getMedicalCost(); BigDecimal deductible = BigDecimal.ZERO; // 宠物险规则:1岁以内宠物,报销比例 70%;1岁以上,报销比例 80% BigDecimal rate = (petDto.getPetAgeMonth() <= 12) ? new BigDecimal("0.7") : new BigDecimal("0.8"); BigDecimal finalAmount = baseAmount.multiply(rate).setScale(2, RoundingMode.HALF_UP); CompensationCalculation calc = new CompensationCalculation(); calc.setBaseAmount(baseAmount); calc.setDeductible(deductible); calc.setFinalAmount(finalAmount); calc.setRuleDescription("宠物医疗险:按年龄分档报销"); return calc; } }

验证方法:

  • 启动项目,用 Postman 调用POST /api/v1/claims/submit,productCode设为pet-medical,petMedical字段填全;
  • 查看返回的compensationCalculation.finalAmount是否按规则计算(如medicalCost=1000,petAgeMonth=6,则finalAmount=700.00)。

6.4 第四步:前端适配:在报案页动态渲染宠物险专属字段

修改前端src/main/webapp/src/views/claims/apply.vue:

<!-- 在表单区域添加条件渲染 --> <template v-if="form.productCode === 'pet-medical'"> <el-form-item label="宠物名称" prop="petMedical.petName"> <el-input v-model="form.petMedical.petName"></el-input> </el-form-item> <el-form-item label="宠物品种" prop="petMedical.petBreed"> <el-input v-model="form.petMedical.petBreed"></el-input> </el-form-item> <el-form-item label="宠物年龄(月)" prop="petMedical.petAgeMonth"> <el-input-number v-model="form.petMedical.petAgeMonth" :min="1"></el-input-number> </el-form-item> <el-form-item label="医疗费用(元)" prop="petMedical.medicalCost"> <el-input v-model="form.petMedical.medicalCost" type="number"></el-input> </el-form-item> </template>

并在data()中初始化petMedical:

data() { return { form: { productCode: '', // ...其他字段 petMedical: { // 新增默认值 petName: '', petBreed: '', petAgeMonth: 12, medicalCost: 0.00, hospitalName: '' } } } }

验证方法:在报案页下拉选择「宠物医疗险」,观察表单是否动态出现宠物相关字段,且提交时能正确传参。

6.5 第五步:上线前必做:配置中心化与灰度开关

真实项目中,新险种上线不能一刀切。我在application.yml中加了一组配置:

feature: pet-medical: enabled: false # 默认关闭,上线前改为 true max-claim-amount: 5000.00 # 宠物险单次最高赔付额 allow-weekend-claim: false # 是否允许周末报案(宠物险暂不支持)

然后在PetMedicalCompensationRule.java的calculate()方法开头加入守卫:

@Value("${feature.pet-medical.enabled:false}") private boolean petMedicalEnabled; @Override public CompensationCalculation calculate(...) { if (!petMedicalEnabled) { throw new BusinessException("宠物医疗险功能暂未开放,请联系管理员"); } // ...原有逻辑 }

我的习惯:从那以后我每次接入新业务线,都强制走一遍「枚举扩展 → DTO 定义 → 策略实现 → 前端适配 → 配置开关」五步,哪怕只是加一个字段,也绝不直接改老代码。这套流程让我在三个不同保险项目中,平均节省 40% 的需求开发时间,更重要的是,线上出问题时,我能 5 秒内定位到是哪个@Component策略类在作祟。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 2:47:13

FTP上传下载实战:从协议原理到lftp/curl脚本避坑指南

简介&#xff1a;这份资源面向需要在Java项目中实现FTP文件传输的开发者&#xff0c;尤其是刚接触网络编程、希望快速跑通上传下载流程的初中级程序员。包内共2个文件&#xff0c;包含1个java源码与1个jar依赖包&#xff0c;压缩包约106KB&#xff0c;体积轻量&#xff0c;便于…

作者头像 李华
网站建设 2026/10/21 2:46:26

MiniC编译器课设拆解:从词法分析到三地址码生成

简介&#xff1a;面向编译原理课程设计的全套参考资源&#xff0c;围绕词法分析、语法分析、符号表管理和Token处理等核心模块展开&#xff0c;适合计算机专业本科生用于课程设计、实验代码调试以及答辩材料准备&#xff0c;尤其适合南京航空航天大学该课程选题参考。压缩包内共…

作者头像 李华
网站建设 2026/10/21 2:46:03

开启labview启用私有属性

请编辑 LabVIEW 配置 文件 以 包含 SuperSecretPrivateSpecialStuffTrue 设置。 "C:\Program Files (x86)\National Instruments\LabVIEW 2020\LabVIEW.ini" 启用SuperSecretPrivateSpecialStuffTrue 禁用SuperSecretPrivateSpecialStuffFalse VI Server - LabVIEW …

作者头像 李华
网站建设 2026/10/21 2:45:28

SpringBoot+jpa适配国产达梦数据库

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 2:45:04

基于深度学习的人脸识别签到系统:从模型训练到部署的完整实践

简介&#xff1a;一套完整的基于深度学习的人脸识别签到系统毕业设计项目&#xff0c;面向计算机相关专业学生、毕业设计开发者&#xff0c;可有效解决传统点名签到效率低、易代签、记录易丢失等问题。项目涵盖人脸注册、特征提取、活体检测、识别签到等核心流程&#xff0c;并…

作者头像 李华
网站建设 2026/10/21 2:43:57

arping 命令详解:局域网 MAC 地址探测与 IP 冲突排查实战

简介&#xff1a;这是一份面向网络管理员、运维工程师及网络编程学习者的 arping 工具源码资源包&#xff0c;聚焦以太网环境下基于 ARP 协议探测主机在线状态并获取其 MAC 地址这一典型场景。资源共 19 个文件&#xff0c;以 cpp 与 h 源文件为核心&#xff0c;涵盖 ARP 报文构…

作者头像 李华