Java保险理赔系统源码:Spring Boot状态机与赔付链路

发布时间:2026/10/29 2:28:51

Java保险理赔系统源码:Spring Boot状态机与赔付链路 简介面向具备Java Web基础、希望理解企业级理赔业务落地的开发者及课程设计、毕业设计学习者这份Java保险理赔系统源码围绕理赔申请提交、审核处理、状态流转到赔付等环节展示MVC分层、Spring/Spring Boot整合、MyBatis持久化、RESTful接口、权限验证与前后端分离的典型实现。压缩包共1729个文件约75.19MB以js、html、css、png/gif等页面与静态资源java、jsp、class、xml配置、sql脚本及jar依赖为主并含readme、properties等说明与配置目录结构便于按业务模块和技术层梳理。已有158人学习可作为保险业务系统开发的参考案例。读者可获取较完整的理赔业务代码骨架、数据库表与SQL、页面交互资源、权限与异常日志处理思路以及接口分层、事务管理和单元测试的实践参照便于对照自身项目查漏补缺理解企业级Java应用的组织方式与常见设计模式。1. 从一张理赔台账说起这套 Java 保险理赔系统到底管什么去年帮一个做车险业务的朋友梳理他们的理赔台账报案、查勘、定损、核赔、支付散在四张 Excel 里一个案子流转到哪一步只有经办人自己心里清楚人一休假就断线。这套 Java 保险理赔系统源码解决的正是这类问题把报案登记、案件受理、查勘定损、核赔审核、赔付支付串成一条带状态的数据流谁在什么时间把案子推到了哪一步全程留痕可查。它的功能没有铺得很开重心压在「一个案子从进来到出去」这条主链上权限、字典、日志这些配套模块也都有够用。适合两类人拿去改一类是想快速搭一套理赔业务底座的开发者另一类是拿它做课程设计或毕设的学生——技术栈是 Spring Boot MyBatis-Plus MySQL 这套主流组合本地跑起来门槛不高二次开发也不用先去啃一堆冷门框架。2. 技术栈与工程结构拿到 zip 先翻这三个地方压缩包解开之后不要急着点启动类先花十分钟把依赖、目录、建表脚本这三样过一遍后面调 bug 会省掉很多来回。理赔类系统和普通 CRUD 项目的差别主要在状态流转和金额计算上所以看结构的时候要重点找「状态字段在哪维护」「金额用什么类型存」这两件事。2.1 技术选型为什么是 Spring Boot MyBatis-Plus 这套组合后端走的是标准的 Spring Boot 单体分层结构Maven 管依赖持久层用 MyBatis-Plus 而不是原生 MyBatis这个选择在理赔场景里挺合理保单、案件、用户、字典这些表绝大多数操作都是单表条件查询加分页MyBatis-Plus 的LambdaQueryWrapper和Page能省掉大量 XML真正复杂的多表统计报表再单独写 XML两边不打架。登录鉴权用 JWT 做无状态会话理赔系统角色多报案员、查勘员、核赔员、财务把权限塞进 token 里比维护 session 省事。前端如果打包在同一个工程里一般是 Vue Element UI 的静态资源放在resources/static下前后端分离的话就单独一个目录。!-- pom.xml 里几个关键依赖版本号以工程实际为准 -- dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 持久层用 MyBatis-Plus单表 CRUD 基本不用写 SQL -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- JWT 做无状态登录多角色场景不依赖 session -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency !-- 参数校验报案表单字段多Valid 能挡掉一半脏数据 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies这里要留意两点。一是mybatis-plus-boot-starter的版本要和 Spring Boot 大版本对齐2.7.x 的 Boot 配 3.5.x 的 MP 是常见组合配错了启动时会在MybatisSqlSessionFactoryBean那里报类型不兼容。二是mysql-connector-java的 scope 是runtime意味着编译期看不到驱动类如果你在代码里直接Class.forName引驱动会编译不过——正常走数据源配置就不受影响。2.2 目录结构controller / service / mapper 三层怎么分工程是典型的单模块分层包名一般按com.xxx.claim打头往下按职责切。理赔业务线不多没必要上微服务多模块一个模块里分清楚就行。claim-system ├── src/main/java/com/xxx/claim │ ├── ClaimApplication.java # 启动类MapperScan 在这里 │ ├── common/ # 统一返回、异常、常量、工具 │ │ ├── Result.java │ │ ├── BizException.java │ │ └── GlobalExceptionHandler.java │ ├── config/ # 拦截器、跨域、MyBatis-Plus 配置 │ ├── controller/ # 只做参数接收和结果包装 │ │ ├── ClaimReportController.java │ │ ├── ClaimAuditController.java │ │ └── ClaimPaymentController.java │ ├── service/ # 业务逻辑状态流转都在这层 │ │ └── impl/ │ ├── mapper/ # 接口 少量复杂 SQL │ ├── entity/ # 与表一一对应的实体 │ ├── dto/ # 入参对象带校验注解 │ └── enums/ # ClaimStatus 这类枚举 └── src/main/resources ├── application.yml ├── mapper/ # 复杂查询的 XML └── db/claim_db.sql # 建库建表脚本先跑这个看目录能发现一个信号enums包单独拎出来了说明状态值是收敛管理的不是散落在各处的字符串魔法值。这一点对理赔系统很关键后面第 3 章会细讲。另外db/claim_db.sql一定要先执行很多新手直接启动然后报「Table claim_db.claim_case doesnt exist」回头翻半天配置其实就是漏了这一步。2.3 数据模型六张核心表撑起整条链路理赔的实体关系其实很线性一份保单可以产生多个案件一个案件对应一次报案、可能多次查勘、一条核赔结论、一条或多条赔付流水。建表脚本里最关键的是下面这几张。-- 案件主表整条链路的锚点status 是核心字段 CREATE TABLE claim_case ( id BIGINT NOT NULL AUTO_INCREMENT, case_no VARCHAR(32) NOT NULL COMMENT 案件号业务唯一, policy_id BIGINT NOT NULL COMMENT 关联保单, accident_time DATETIME NOT NULL COMMENT 出险时间, accident_type VARCHAR(32) DEFAULT NULL COMMENT 事故类型字典值, claim_amount DECIMAL(14,2) DEFAULT 0.00 COMMENT 报案金额, paid_amount DECIMAL(14,2) DEFAULT 0.00 COMMENT 实赔金额, status INT NOT NULL DEFAULT 10 COMMENT 状态码见 ClaimStatus, report_user_id BIGINT NOT NULL COMMENT 报案人, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_case_no (case_no), -- 同一保单同一次出险只允许报一次案靠唯一索引兜底防重 UNIQUE KEY uk_policy_accident (policy_id, accident_time), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 状态流水表谁在什么时候把案子推到了哪一步全靠它 CREATE TABLE claim_status_log ( id BIGINT NOT NULL AUTO_INCREMENT, case_id BIGINT NOT NULL, from_status INT DEFAULT NULL, to_status INT NOT NULL, operator_id BIGINT NOT NULL, remark VARCHAR(255) DEFAULT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_case (case_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段选型上有个细节值得说金额一律用DECIMAL(14,2)不用DOUBLE。理赔金额要参与免赔额扣减、比例计算、上限截断浮点数在这种多步运算里会出现0.009999这类误差最后财务对账对不上查起来是纯玄学。status用INT而不是VARCHAR是因为状态要参与比较和流转校验数字比字符串稳定也方便加索引过滤。uk_policy_accident这个联合唯一索引是防重复报案的最后一道闸业务层也会判一次但并发下还是得靠数据库。3. 核心链路拆解一个理赔案件从报案到结案的完整流转这条链路是整套源码里最值得逐行读的部分。报案只是入口真正决定系统能不能用下去的是中间的状态管理和金额计算——状态乱了案子会卡死在某一步推不动金额算错了财务那边直接拒收。3.1 报案入口Controller 只做校验业务全在 ServiceController 层保持极薄只负责接收参数、取当前登录人、把异常往上抛业务判断全部下沉到 Service。这样做的直接好处是后面加定时任务或者批量导入的时候可以直接调 Service不用去模拟一个 HTTP 请求。RestController RequestMapping(/api/claim) public class ClaimReportController { Autowired private ClaimCaseService claimCaseService; /** * 报案登记提交保单号 出险信息落一条 claim_case 主记录 * 这一步只登记不做定损状态推进到 REPORTED */ PostMapping(/report) public ResultLong report(RequestBody Valid ClaimReportDTO dto, HttpServletRequest request) { // 操作人从 JWT 里取绝不信任前端传的 userId Long operatorId JwtUtil.getUserId(request); if (dto.getAccidentTime().isAfter(LocalDateTime.now())) { throw new BizException(出险时间不能晚于当前时间); } Long caseId claimCaseService.createCase(dto, operatorId); return Result.ok(caseId); } }Valid配合 DTO 上的NotBlank、NotNull能把空保单号、空出险时间这类脏数据挡在 Service 之前。JwtUtil.getUserId从 token 解析是权限校验的第一道关。返回ResultLong而不是整个实体是因为前端拿到 caseId 之后紧接着要跳详情页返回太多字段反而增加序列化开销也让接口契约变模糊。Service 里才是真正的活Override Transactional(rollbackFor Exception.class) public Long createCase(ClaimReportDTO dto, Long operatorId) { // 1. 保单必须存在且在有效期内这是后面所有金额计算的前提 Policy policy policyMapper.selectByPolicyNo(dto.getPolicyNo()); if (policy null) { throw new BizException(保单不存在); } if (policy.getExpireDate().isBefore(dto.getAccidentTime().toLocalDate())) { throw new BizException(出险时间不在保单有效期内); } // 2. 组装主记录状态初始化为已报案 ClaimCase entity new ClaimCase(); entity.setCaseNo(generateCaseNo()); entity.setPolicyId(policy.getId()); entity.setAccidentTime(dto.getAccidentTime()); entity.setAccidentPlace(dto.getAccidentPlace()); entity.setAccidentType(dto.getAccidentType()); entity.setClaimAmount(dto.getClaimAmount()); entity.setStatus(ClaimStatus.REPORTED.getCode()); entity.setReportUserId(operatorId); claimCaseMapper.insert(entity); // 3. 状态流水必须写出问题查谁改的全靠这张表 statusLogMapper.insert(ClaimStatusLog.of(entity.getId(), null, ClaimStatus.REPORTED, operatorId, 报案登记)); return entity.getId(); }Transactional(rollbackFor Exception.class)里的rollbackFor不能省Spring 默认只对RuntimeException回滚BizException如果继承的是受检异常主表插进去了、流水没插数据就残了。generateCaseNo()一般是「前缀 yyyyMMdd 6 位序列」序列用 Redis 自增或者数据库号段表都行注意别用System.currentTimeMillis()拼随机数高并发下会撞。3.2 状态机把 status 字段从字符串魔法值里救出来理赔系统最容易翻车的地方就是状态管理。见过太多项目在 Service 里写if (已报案.equals(status))加一个「拒赔」状态就得全局搜一遍字符串。这套源码的做法是用枚举加流转表状态推进必须先过校验。public enum ClaimStatus { REPORTED(10, 已报案), SURVEYING(20, 查勘中), SURVEYED(30, 已定损), AUDITING(40, 核赔中), APPROVED(50, 核赔通过), REJECTED(55, 核赔拒赔), PAID(60, 已赔付), CLOSED(70, 已结案); private final int code; private final String desc; ClaimStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } public static ClaimStatus of(int code) { for (ClaimStatus s : values()) { if (s.code code) return s; } throw new BizException(未知状态码: code); } }枚举只定义了「有哪些状态」真正约束流转的是下面这张静态表public final class ClaimStatusMachine { // key 是当前状态value 是允许流转到的下一批状态 private static final MapClaimStatus, SetClaimStatus ALLOWED new EnumMap(ClaimStatus.class); static { ALLOWED.put(ClaimStatus.REPORTED, EnumSet.of(ClaimStatus.SURVEYING, ClaimStatus.CLOSED)); ALLOWED.put(ClaimStatus.SURVEYING, EnumSet.of(ClaimStatus.SURVEYED, ClaimStatus.CLOSED)); ALLOWED.put(ClaimStatus.SURVEYED, EnumSet.of(ClaimStatus.AUDITING)); ALLOWED.put(ClaimStatus.AUDITING, EnumSet.of(ClaimStatus.APPROVED, ClaimStatus.REJECTED)); ALLOWED.put(ClaimStatus.APPROVED, EnumSet.of(ClaimStatus.PAID)); ALLOWED.put(ClaimStatus.REJECTED, EnumSet.of(ClaimStatus.CLOSED)); ALLOWED.put(ClaimStatus.PAID, EnumSet.of(ClaimStatus.CLOSED)); ALLOWED.put(ClaimStatus.CLOSED, EnumSet.noneOf(ClaimStatus.class)); } /** * 状态推进统一入口校验 更新 记流水三步绑在一个事务里 */ public static void transfer(ClaimCase c, ClaimStatus target, Long operatorId, String remark) { ClaimStatus current ClaimStatus.of(c.getStatus()); if (!ALLOWED.get(current).contains(target)) { throw new BizException( String.format(不允许从[%s]流转到[%s], current.getDesc(), target.getDesc())); } c.setStatus(target.getCode()); // 调用方负责 update 主表和插流水这里只做校验和赋值 } }把流转规则抽成表之后加一个新状态的改动成本就很低枚举里加一行流转表里加一对映射业务代码基本不用动。我一般还会在claim_case表上加个version字段做乐观锁两个人同时审核同一个案子时后提交的会拿到更新失败而不是把前一个人的结论覆盖掉。3.3 核赔计算免赔额、赔付比例和保额上限怎么落地赔付金额的计算看着简单实际是最容易出争议的环节。常见的规则是损失金额先扣免赔额剩下的乘赔付比例最后不能超过保额上限。/** * 计算应赔金额 * param lossAmount 定损金额来自查勘环节 * param deductible 免赔额保单条款里约定 * param rate 赔付比例比如 0.8 表示赔 80% * param limit 保额上限超出部分不赔 */ public BigDecimal calculatePayable(BigDecimal lossAmount, BigDecimal deductible, BigDecimal rate, BigDecimal limit) { // 空值兜底宁可赔 0 也不能 NPE 把整个审核流程打断 if (lossAmount null || lossAmount.compareTo(BigDecimal.ZERO) 0) { return BigDecimal.ZERO; } BigDecimal base lossAmount.subtract(deductible null ? BigDecimal.ZERO : deductible); if (base.compareTo(BigDecimal.ZERO) 0) { return BigDecimal.ZERO; } // 先乘比例再四舍五入到分顺序反了会出现分位误差 BigDecimal payable base.multiply(rate null ? BigDecimal.ONE : rate) .setScale(2, RoundingMode.HALF_UP); return payable.min(limit null ? payable : limit); }几个参数要特别注意。deductible是绝对免赔额如果条款写的是「损失金额的 10%」那就得先算出相对免赔额再传进来不要混着用。setScale(2, RoundingMode.HALF_UP)必须显式指定舍入模式BigDecimal用divide时不指定会抛ArithmeticException而乘法的 scale 会跟着操作数走不收敛的话数据库存进去会被截断。payable.min(limit)这一步很多人会漏结果出现「赔得比保额还多」的离谱数据。3.4 赔付与流水幂等和事务边界财务付款是所有业务系统里最不能出错的一步重复打款比不打款更难处理。理赔系统里常见做法是引入一个幂等号由前端在进入支付页面时生成同一个幂等号只允许成功一次。Override Transactional(rollbackFor Exception.class) public void pay(Long caseId, BigDecimal amount, String requestId, Long operatorId) { // 1. 幂等校验request_id 在流水表上有唯一索引重复请求直接返回 ClaimPayment exist paymentMapper.selectByRequestId(requestId); if (exist ! null) { return; // 已经处理过静默返回前端拿到的是成功 } ClaimCase c claimCaseMapper.selectById(caseId); ClaimStatus current ClaimStatus.of(c.getStatus()); if (current ! ClaimStatus.APPROVED) { throw new BizException(只有核赔通过的案件才能赔付当前状态 current.getDesc()); } // 2. 写赔付流水这里用插入而不是更新保留完整的支付历史 ClaimPayment payment new ClaimPayment(); payment.setCaseId(caseId); payment.setRequestId(requestId); payment.setAmount(amount); payment.setPayStatus(1); payment.setOperatorId(operatorId); paymentMapper.insert(payment); // 3. 回写案件实赔金额并推进状态两步在同一事务内 c.setPaidAmount(amount); ClaimStatusMachine.transfer(c, ClaimStatus.PAID, operatorId, 财务赔付完成); claimCaseMapper.updateById(c); statusLogMapper.insert(ClaimStatusLog.of(caseId, current, ClaimStatus.PAID, operatorId, 赔付)); }幂等的关键不在selectByRequestId那次查询而在流水表上request_id的唯一索引——两个请求同时穿过查询时数据库会拦住第二条。所以paymentMapper.insert抛DuplicateKeyException是预期行为可以在全局异常处理器里转换成「重复提交」的友好提示而不是当成系统错误。另外注意pay方法把「插流水」和「改主表」放在同一个事务中途失败会整体回滚不会出现「钱记了但案子没推进」的半截状态。4. 跑起来本地建库、改配置、走通第一笔理赔前面看的是设计现在落到操作。这一套流程走完本机应该能起服务、能登录、能提交一笔报案并推到核赔通过。4.1 环境准备与建库需要的东西不多JDK 8 或 11、Maven 3.6、MySQL 5.7 或 8.0。Redis 是可选的只有在用号段模式生成案件号或者做 token 黑名单时才需要工程里一般会做成可开关的配置没装也不影响主流程。# 1. 建库字符集一定要 utf8mb4事故描述里可能有生僻字 mysql -uroot -p -e CREATE DATABASE claim_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; # 2. 导入建表脚本路径以实际工程为准 mysql -uroot -p claim_db src/main/resources/db/claim_db.sql # 3. 确认表都建出来了核心应该是 6 张左右 mysql -uroot -p claim_db -e SHOW TABLES;脚本里一般会带几条初始化数据一个管理员账号、几个角色、几条字典项。如果执行完发现sys_user是空的说明脚本分成了建表和初始化两个文件得挨个跑一遍别只跑了第一个。4.2 配置修改与启动application.yml里要改的基本只有数据源和文件上传路径其他保持默认。server: port: 8080 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/claim_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: # 理赔材料要传照片和 PDF默认 1MB 肯定不够 max-file-size: 20MB max-request-size: 60MB mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml configuration: map-underscore-to-camel-case: true # 下划线转驼峰实体不用逐个写 TableField log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发期打 SQL上线记得关 claim: upload-path: /data/claim/upload # 附件落盘目录Windows 下换成 D:/claim/uploadserverTimezoneAsia/Shanghai这个参数不要省MySQL 8 的驱动在没指定时区时会按 UTC 解析出险时间和创建时间会整体差 8 小时排查起来非常费劲。allowPublicKeyRetrievaltrue是 MySQL 8 默认加密方式下本地连接需要的不加会报Public Key Retrieval is not allowed。启动就是标准的mvn spring-boot:run或者直接跑ClaimApplication。看到控制台打出Started ClaimApplication in x.xxx seconds就说明起来了接下来访问http://127.0.0.1:8080默认账号密码一般在claim_db.sql的注释里或者 README 里。4.3 走通第一笔理赔接口验证清单页面能打开不代表链路通建议按状态顺序手动打一遍接口出问题能立刻定位到是哪一环。# 1. 登录拿 token curl -X POST http://127.0.0.1:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} # 2. 报案返回 caseId curl -X POST http://127.0.0.1:8080/api/claim/report \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {policyNo:P20240001,accidentTime:2024-05-12 09:30:00,accidentPlace:某路段,accidentType:COLLISION,claimAmount:8000.00} # 3. 查勘定损推进到 SURVEYED curl -X POST http://127.0.0.1:8080/api/claim/survey \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {caseId:1,lossAmount:6500.00,surveyRemark:左前叶子板更换} # 4. 提交核赔 curl -X POST http://127.0.0.1:8080/api/claim/audit \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {caseId:1,approved:true,remark:材料齐全} # 5. 财务赔付requestId 由前端生成重放同一个值应当返回成功但不重复记账 curl -X POST http://127.0.0.1:8080/api/claim/pay \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {caseId:1,amount:5200.00,requestId:req-20240512-0001}每一步之后去数据库里确认一下claim_case.status有没有跟着变claim_status_log里有没有多出一条记录。如果状态没动但接口返回成功多半是 Service 里漏了updateById或者事务没生效——Transactional加在 private 方法或者同类内部调用的方法上是无效的这个坑后面还会提到。5. 踩坑排查启动报错、权限 403 和金额精度这几个坑这套代码在不同机器上跑翻车点集中在这几处。下面每一条都是「现象 → 原因 → 解决」的结构对照着看能省不少时间。启动报Invalid bound statement (not found)现象是服务能起来但一调接口就抛这个异常日志里能看到 Mapper 接口名。原因是mapper-locations配的路径和 XML 实际位置对不上或者 Maven 打包时把src/main/java下的 XML 过滤掉了。MyBatis 默认只扫resources下的 XML如果你把 XML 放在了java包路径旁边就得在pom.xml的buildresources里显式把**/*.xml包含进来。解决办法有两个要么统一把 XML 挪到resources/mapper要么补上 resources 配置我一般选前者省得后面换构建工具又出问题。登录后所有接口返回 403控制台没有异常现象是能拿到 token但带 token 请求一直 403。原因通常是 JWT 过滤器注册的顺序不对或者拦截器把/api/auth/login也拦了。Spring Security 的过滤器链有明确顺序自定义的 JWT 过滤器要放在UsernamePasswordAuthenticationFilter之前如果是用拦截器方案WebMvcConfigurer的addInterceptors里一定要excludePathPatterns(/api/auth/**, /error)否则登录接口自己也被拦形成死循环。排查时把日志级别调到 DEBUG看SecurityContextHolder里有没有塞进Authentication一眼就能分清是没解析还是没放行。出险时间和创建时间相差 8 小时现象是前端显示的时间和数据库里的时间对不上差整 8 小时。原因是 MySQL 连接串没指定serverTimezone驱动按 UTC 解析DATETIME字面量。解决就是在 URL 后面加serverTimezoneAsia/Shanghai同时确认 MySQL 服务端的time_zone也是东八区。另外一个容易忽略的点实体里用LocalDateTime时MyBatis-Plus 默认的LocalDateTimeTypeHandler会按 JVM 时区处理如果服务器时区不是东八区光改连接串也不够得在启动参数里加-Duser.timezoneAsia/Shanghai。赔付金额出现 0.01 的误差现象是审核页面算出来的金额和财务复核的金额差一分钱。原因基本锁定在金额用了double或者Float多步乘除之后精度丢失。解决是把所有涉及金额的字段、参数、中间变量全部换成BigDecimal并且每次乘除都显式setScale(2, RoundingMode.HALF_UP)。还有一条血泪经验BigDecimal.equals会比较 scalenew BigDecimal(1.0).equals(new BigDecimal(1.00))是false做金额相等判断一律用compareTo(...) 0。同一笔赔付被记了两次现象是财务那边说打了两笔但系统里只该有一笔。原因是支付接口没有幂等控制用户手抖点了两次或者网络超时后前端重试。解决是前端在进入支付页时生成一个requestId并随请求带上后端在流水表的request_id上建唯一索引插入冲突时捕获DuplicateKeyException并返回「已处理」。注意requestId不能用caseId代替同一个案子可能有分次赔付用案件号做幂等键反而会挡住正常的第二次付款。6. 二次开发给理赔系统加一个自动立案规则引擎案子量上来之后人工判断「这笔报案要不要立案」会变成瓶颈。我一般会在报案和查勘之间插一个轻量的规则引擎用配置化的方式决定案件是自动通过、转人工还是直接拒赔改造点不多收益挺明显。落地的位置选在ClaimCaseServiceImpl.createCase的最后一步主记录和流水写完但事务还没提交的时候。规则本身存一张claim_rule表字段包括rule_code、condition_json、action、priority和enabled条件用 JSON 描述比如「事故类型为 COLLISION 且报案金额小于 5000」。/** * 自动立案判定 * param c 已落库的案件 * return AUTO_PASS / MANUAL / REJECT */ public RuleAction evaluate(ClaimCase c) { // 按优先级升序取启用的规则命中第一条就返回避免规则互相打架 ListClaimRule rules ruleMapper.selectEnabledOrderByPriority(); for (ClaimRule rule : rules) { JsonNode cond JsonUtil.readTree(rule.getConditionJson()); boolean hit match(cond, c); if (hit) { log.info(案件 {} 命中规则 {}, c.getCaseNo(), rule.getRuleCode()); return RuleAction.valueOf(rule.getAction()); } } // 一条规则都没命中默认转人工宁可多看一眼也不要误放 return RuleAction.MANUAL; }match方法按字段名反射取值再比较条件 JSON 里支持eq、gt、lt、in四种操作符就够用了别一上来就上 Drools那套东西学习成本高、调试也不直观。规则命中后写一条claim_rule_hit记录把命中的规则码和当时的案件快照存下来后面业务方质疑「为什么这单自动过了」直接拿记录说话。验证方式也简单造三组数据一组金额小、类型常见的应该走AUTO_PASS直接推到查勘一组金额大或者类型罕见的走MANUAL一组保单已过期的走REJECT。跑完查claim_status_log和claim_rule_hit两张表状态和规则码都对得上改造就算成了。注意规则引擎本身要包在报案那个事务里规则判定抛异常时整笔报案应该回滚而不是留下一个状态不明的孤儿案件。从那以后我每次往业务系统里加这类「自动判定」模块都强制先把「没命中任何规则时怎么办」写死在代码里默认转人工绝不留空。规则可以慢慢补但系统不能因为少配一条规则就把案子放行或者卡死。这套 Java 保险理赔系统源码的结构足够清爽改起来不费劲希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/29 2:28:51

Winform登录窗体源码精讲:无边框圆角、DPAPI与高DPI适配

简介:面向Winform桌面开发初学者与界面仿真实战练习者的一份QQ登录界面源码工程,围绕不依赖第三方控件、仅用内置组件复刻QQ2013登录窗口这一目标,提供可参考的窗体布局、控件属性设置与登录交互实现。压缩包共129个文件,以93张pn…

2026/10/29 2:28:51

自适应模糊控制:从Mamdani到T-S的参数在线整定与避坑

简介:面向模糊控制学习者与工程实践者的自适应模糊控制器设计资料包,围绕输入模糊化、规则库构建、模糊推理、去模糊化及在线参数调整展开,适合已具备控制理论基础、希望借助 MATLAB/Simulink 完成建模与仿真验证的读者,兼顾理论梳…

2026/10/29 2:28:51

CRNN+CTC字符型验证码识别全流程与Python实现

简介:面向深度学习入门与计算机视觉实践的字符型数字验证码识别项目,围绕卷积神经网络与循环神经网络的组合建模,讲解从数据集准备、图像归一化与数据增强、字符序列编码,到模型构建、训练评估与推理的完整流程,并给出…

2026/10/29 5:03:59

POP设计提升零售转化率的关键要素与实践

1. 项目概述:POP知识点解析POP(Point of Purchase)作为零售营销领域的重要概念,指的是消费者实际完成购买决策的关键接触点。这个概念最早起源于上世纪30年代的美国超市货架陈列研究,如今已发展成为涵盖实体零售终端、…

2026/10/29 5:03:59

深入解析上下文模式:从设计原则到异步传播的工程实践

1. 从“上下文模式”说起:一个被低估的工程概念第一次看到context-mode这个词,很多人会下意识地把它归到某个具体框架的配置项里,比如某个前端库的渲染模式、某个数据库的连接模式,或者某个 AI 工具的对话模式。但真正在工程一线待…

2026/10/29 5:03:59

AI学术写作助手PaperPower:从文献检索到论文润色全流程优化

1. 项目概述:当学术研究遇上AI助手去年帮导师审阅研究生论文时,发现一个有趣现象:80%的学术写作问题其实都有规律可循。那些反复出现的文献格式错误、逻辑断层和表达冗余,完全可以通过智能工具预先规避。这正是PaperPower诞生的背…

2026/10/29 5:03:59

从零搭建可复用技能模块:skills 设计思路与实操避坑指南

1. 从“skills”这个热词说起:它到底是什么最近几个月,不管是在技术社区、开发者群聊,还是在各类效率工具的讨论区,“skills”这个词出现的频率高得离谱。有人把它当成一个插件系统,有人把它当成一套提示词模板&#x…

2026/10/29 4:58:59

Word三线表跨页断行与表头重复:底层逻辑与实操避坑指南

论文写到第三章,表格一多,最怕的就是跨页。上个月帮一位做实证分析的朋友改稿,他发来一张截图,一个回归结果表被分页符拦腰截断,第二页的表头凭空消失,底下还多出一行孤零零的“注:***表示……”…

2026/10/28 14:42:12

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/28 15:28:56

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑