news 2026/10/3 21:23:29

SpringBoot+Vue+MySQL城乡居民医保系统:从业务设计到联调全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue+MySQL城乡居民医保系统:从业务设计到联调全解析

每年到毕设季,我都能在技术群里见到一批同学拿着“城乡居民基本医疗信息管理系统”这个题目问怎么下手。说实话,这个题目看着就是一堆增删改查:维护参保人、录缴费记录、算报销金额、出统计报表。但真正动手之后你会发现,后面的“报销比例怎么算”“跨年度缴费怎么处理”“审核状态怎么流转”这些问题,每一个都能让你在电脑前坐一晚上。我当初做这个题目的时候也踩了不少坑,这篇文章就把整个系统的业务逻辑、技术选型、数据库设计、核心代码实现和前后端联调的细节一次讲透,适合正在做毕设、课设,或者想用SpringBoot+Vue+MySQL练手的学习型项目参考。

整篇内容会严格围绕这个项目的源代码展开,重点解释那些“网上抄不到”的设计思路和实现细节。

1. 先把业务搞清楚:城乡居民医保系统到底管什么

1.1 这个系统和“医院HIS系统”完全不是一回事

很多同学第一次接触这个题目,会下意识把它理解成医院里的门诊住院管理系统,然后跑去研究挂号、开药、电子病历,方向一下子就偏了。

城乡居民基本医疗信息管理系统,本质上是一个社保业务经办平台,它管的是“钱”和“人”的关系,而不是“病”和“药”的关系。核心要处理的几类对象:

  • 参保人:也就是居民本人,系统要管他的身份信息、户籍信息、参保状态。
  • 缴费记录:每年按规定缴纳的医保费用,系统要记录缴没缴、缴了多少、缴到哪一年。
  • 报销申请:居民看病发生医疗费用后,提交报销单据,系统按规则计算出可以报销多少钱,并走审核流程。
  • 定点医疗机构:医院、社区卫生服务中心、药店等,存在基础档案里,报销时根据医院级别决定报销比例。
  • 系统用户:管理员、业务经办人员、审核人员、查询统计人员等。

所以你在设计系统的时候,脑子里要有一条完整的数据链路:参保人建档 -> 年度缴费 -> 看病产生费用 -> 提交报销申请 -> 系统计算报销金额 -> 审核 -> 支付 -> 汇总统计。这才是一个合格的医保管理业务系统,而不是一个莫名其妙的“医院挂号平台”。

1.2 角色梳理:谁在用什么功能

没有真实需求文档的时候,角色和功能全靠业务常识推。我的做法是先列角色,再给每个角色列操作清单,功能模块自然就出来了。

角色核心操作
系统管理员用户管理、角色分配、参数配置(报销比例、起付线、封顶线)、数据字典维护
经办人员参保人登记、信息修改、缴费录入、报销申请初审
审核人员报销单复审、驳回或通过、支付状态确认
查询统计人员参保情况统计、缴费情况统计、报销支出统计、明细导出

这里有个很重要的取舍:城乡居民医保系统在真实业务里往往还有居民自助端,让居民自己在网上查缴费记录、看报销进度。但毕设项目我建议一开始别碰居民端,先把管理端做得像样,再有余力可以加一个独立的Vue页面做查询,前后端分离的技术栈做这个扩展并不难。

1.3 功能边界怎么控制:不要一上来就需求膨胀

我见过很多项目计划书写了十几个模块,真做的时候连登录都写不明白。这个题目的合理功能边界应该是“四大模块 + 系统管理”:

  • 参保管理:参保人信息的新增、修改、查询、状态变更。
  • 缴费管理:按年度缴费记录,缴费状态的校验、欠费提醒、缴费记录查询。
  • 报销管理:报销申请的提交、金额自动计算、多级审核、支付状态跟踪。这是系统的核心,也是面试和答辩时最容易被追问的部分。
  • 统计报表:参保人数、缴费金额、报销支出、医院报销排行等,用柱状图或表格展示。
  • 系统管理:用户、角色、菜单权限、操作日志、数据字典。

把边界控制在这个范围,开发量完全可控,同时又足够体现架构能力。如果你一开始就想把所有真实医保业务都塞进来,比如大病保险、门诊慢特病、异地就医结算、家庭共济这些,项目基本就烂尾了。

2. 技术栈选型的底层逻辑:为什么偏偏是SpringBoot+Vue+MySQL

2.1 后端:SpringBoot比SSM更适合这类学习项目

如果是五六年前,很多人会推荐SSM(Spring + SpringMVC + MyBatis)组合。但现在的实际情况是:SpringBoot已经成了绝大多数公司新项目的标配,面试时也默认你会SpringBoot。它解决了SSM最大的痛点——繁琐的XML配置和环境搭建。

用SpringBoot做这个项目,核心收益有几点:

  • starter机制:引入spring-boot-starter-web、spring-boot-starter-security、mybatis-plus-boot-starter,依赖版本由框架统一管理,再也不会出现“包冲突搞一天”。
  • 内置Tomcat:java -jar启动,部署和演示都方便,毕设答辩时老师会让你现场跑起来看,一条命令启动比配置外部Tomcat省心太多。
  • 结构分层清晰:controller、service、mapper、entity、config各司其职,也方便后面讲架构。

2.2 前端:Vue负责“管理后台体验”

单页应用的优势在这个项目里体现得很明显:左侧菜单切换到不同管理页面不用重新加载,axios请求后端接口,操作反馈快,页面也不需要刷新。关键是Vue的生态成熟,组件化拆分让每个功能模块像一个独立积木,整改起来心智负担小。

版本选择上:

  • Vue 2 + Element UI:网上案例最多,找资料容易,老一点的教学视频基本都是这套。
  • Vue 3 + Element Plus:新项目推荐,但如果导师或参考资料都是Vue 2,选Vue 2也完全够用,毕竟管理系统核心是数据展示和表单交互,不依赖响应式语法的高级特性。

另外要提一个部署细节:Vue项目npm run build之后生成dist目录。最省事的做法是配置axios的baseURL为绝对路径(例如 http://localhost:8080/api),SpringBoot跑在8080端口,前端用Vue dev server跑在5173或8081,开发环境通过跨域配置联调。生产演示时再用nginx把前端dist包和SpringBoot反向代理到一起。如果你不想引入nginx,也可以直接把dist目录拷进SpringBoot的src/main/resources/static下,后端一jar包全带走了,对毕设演示来说这招非常实用。

2.3 数据库:MySQL和ORM框架的选择

MySQL是这类项目最稳妥的选择,原因很简单:资料多、Navicat可视化管理方便、绝大多数云服务器都支持。版本建议MySQL 8.0+,性能更好,SQL窗口函数等特性也更丰富。

ORM层我推荐MyBatis-Plus而不是原生MyBatis或JPA。理由很直接:

  • 单表CRUD不需要写SQL,内置BaseMapper就能搞定。
  • 分页查询有内置插件,不用手写limit和count。
  • 条件构造器LambdaQueryWrapper写查询条件非常直观。

JPA在这个项目里也能用,但JPA的级联关系、懒加载问题对于初学者来说,踩坑成本比MyBatis-Plus高不少。MyBatis-Plus配合逻辑删除注解@TableLogic,做“删参保人”这类功能时不用真的DELETE,一个注解就搞定了。

3. 数据建模与接口设计:把业务翻译成表和API

3.1 核心数据表设计:字段怎么定,主外键怎么处理

数据库设计是整个系统最见功底的部分。一张表字段随便加,但后面写业务代码的时候发现设计不对,改起来想哭。我给出一个经过实践验证的5张核心表,覆盖这个项目的全部主业务。

参保人表(insured_person)

字段名类型说明
idbigint主键,自增
id_cardvarchar(18)身份证号,唯一索引,业务核心标识
namevarchar(50)姓名
gendertinyint性别 0未知 1男 2女
birth_datedate出生日期
household_addressvarchar(200)户籍地址
insured_statustinyint参保状态 0暂停 1正常 2退保
create_timedatetime创建时间
update_timedatetime更新时间

为什么要用id_card做唯一索引?因为医保业务天然就以身份证号作为人的唯一标识,同一参保人不能重复建档。这个唯一约束在代码层也要做校验,否则并发下会插入重复数据。

缴费记录表(payment_record)

字段名类型说明
idbigint主键
person_idbigint参保人ID,逻辑外键
yearint缴费年度,如2025
amountdecimal(10,2)缴费金额
pay_timedatetime缴费时间
statustinyint0未到账 1已到账
create_timedatetime创建时间

注意amount用DECIMAL而不是float/double,这个是所有涉及金额系统的通识,后面我会专门讲为什么。

报销申请表(reimbursement_apply)

字段名类型说明
idbigint主键
person_idbigint参保人ID
hospital_idbigint定点医院ID
reimburse_typetinyint1普通门诊 2住院 3大病
total_amountdecimal(10,2)总医疗费用
start_linedecimal(10,2)起付线
ratiodecimal(5,2)报销比例,如0.7表示70%
ceiling_linedecimal(10,2)封顶线
reimburse_amountdecimal(10,2)最终报销金额
statustinyint0待审核 1初审通过 2终审通过 3已驳回 4已支付
apply_timedatetime申请时间
audit_remarkvarchar(255)审核备注

这张表比较有意思的点:起付线、比例、封顶线这些值,我建议设计成报销申请单上的“快照字段”,而不是每次计算时去查当前配置。为什么?因为报销规则会调整,你今年按70%算的,明年政策变了,历史单据要按当时规则留存,这个就是业务上的“数据快照”思想。毕设答辩时把这个点讲出来,是一个亮点。

定点医疗机构表(medical_org)

字段名类型说明
idbigint主键
org_namevarchar(100)机构名称
org_leveltinyint1社区 2二级 3三级
org_typetinyint1医院 2药房
addressvarchar(200)地址
contactvarchar(50)联系人

报销规则配置表(reimburse_rule)

字段名类型说明
idbigint主键
rule_namevarchar(50)规则名称,如“住院三级医院”
reimburse_typetinyint对应报销类型
org_leveltinyint对应医院级别
start_linedecimal(10,2)起付线
ratiodecimal(5,2)报销比例
ceiling_linedecimal(10,2)封顶线

这张表的作用是让管理员可以在页面上灵活配置报销规则,而不用改代码。有了它,你的报销计算逻辑就能写成一个通用方法,从规则表里查参数。

外键策略上,我强烈建议不要建物理外键约束,只保留person_id这种逻辑外键,关联关系在应用层维护。原因:物理外键会阻止删除、影响写入性能,而且MyBatis-Plus做分页关联查询时物理外键并没有什么帮助。真实的开发规范里也普遍是逻辑外键,这个习惯越早养成越好。

3.2 接口设计:Restful API怎么划分

后端接口路径我推荐按资源来划分:

  • /api/insured:参保人增删改查、状态变更
  • /api/payment:缴费记录的录入、查询、校验
  • /api/reimbursement:报销申请的提交、审核、计算
  • /api/org:定点机构管理
  • /api/rule:报销规则配置
  • /api/report:统计数据接口
  • /api/auth:登录、获取用户信息
  • /api/system:用户、角色、菜单

所有接口统一返回一个结构体,前端好处理:

{ "code": 200, "message": "success", "data": { } }

分页接口统一接收current和size参数,返回records、total、pages等字段。用MyBatis-Plus的Page<T>对象直接返回即可,前端配合Element UI的el-table和el-pagination非常顺手。

3.3 数据字典:一张“表驱动”的清单

系统中大量使用状态值和枚举值:参保状态、报销状态、医院级别、报销类型。我建议做一张数据字典表来管理,而不是把这些枚举写死在代码里。

字典表的核心思路就两个字段:type标识字典类型,value存实际值。比如:

  • type=insured_status,value=1,label=正常参保。
  • type=reimburse_type,value=2,label=住院。

这样做的好处:前端下拉框可以动态拉数据,不用改前端代码;新增一个医院级别也不用后端发版。虽然这种设计在简单系统里有点“重”,但既然是学习项目,体现出业务建模的通用性,在答辩时是很加分的设计。

4. 核心业务逻辑实现:参保、缴费、报销三条主线的代码级拆解

4.1 参保登记:身份证唯一性和状态流转

参保人的新增和修改,最基础也最容易出问题的是身份证校验。

@Service public class InsuredPersonServiceImpl extends ServiceImpl<InsuredPersonMapper, InsuredPerson> { public void addInsured(InsuredPersonDTO dto) { // 1. 身份证号格式校验(15/18位,最后一位可能是X) boolean idCardValid = IdCardUtils.validate(dto.getIdCard()); if (!idCardValid) { throw new BusinessException("身份证号格式不正确"); } // 2. 身份证号唯一性校验 long count = lambdaQuery() .eq(InsuredPerson::getIdCard, dto.getIdCard()) .count(); if (count > 0) { throw new BusinessException("该身份证号已存在参保记录"); } // 3. 出生日期从身份证号中解析,避免用户填错 String birth = dto.getIdCard().substring(6, 14); dto.setBirthDate(LocalDate.parse(birth, DateTimeFormatter.ofPattern("yyyyMMdd"))); // 4. 初始参保状态为正常 dto.setInsuredStatus(InsuredStatusEnum.NORMAL.getCode()); this.save(BeanCopyUtils.copy(dto, InsuredPerson.class)); } }

性别和出生日期我建议直接从身份证号码里解析,而不是让操作员手动选。这个细节属于“做了就很专业、不做也不影响功能”的那种,但答辩时讲到这一步,老师会觉得你确实考虑过真实业务。身份证第17位奇数为男、偶数为女,第7到14位是出生日期,写两个工具方法就搞定。

参保状态的变化做一个最简单的状态机:正常 -> 暂停 -> 退保,同时禁止已退保的人再次缴费。这个状态机用枚举加上一个allowedTransitions映射表来实现:

@Getter public enum InsuredStatusEnum { PAUSE(0, "暂停"), NORMAL(1, "正常"), CANCEL(2, "退保"); private final int code; private final String desc; public static final Map<Integer, Set<Integer>> ALLOWED_TRANSITIONS = new HashMap<>() {{ put(NORMAL.getCode(), new HashSet<>(Arrays.asList(PAUSE.getCode(), CANCEL.getCode()))); put(PAUSE.getCode(), new HashSet<>(Collections.singletonList(NORMAL.getCode()))); put(CANCEL.getCode(), new HashSet<>()); // 退保为终态 }}; public static void checkTransition(int from, int to) { if (!ALLOWED_TRANSITIONS.getOrDefault(from, new HashSet<>()).contains(to)) { throw new BusinessException("非法的参保状态变更"); } } }

状态机把业务校验集中到枚举里,后续任何service方法要改状态,先调checkTransition,从源头杜绝了“从退保直接跳到正常”这种逻辑漏洞。

4.2 缴费记录:年度校验是重点

缴费业务的本质是“某个参保人在某一年交了一笔钱”,所以核心校验有两个:

  1. 这个参保人当前状态必须是正常或暂停,退保人员不能缴费。
  2. 同一个参保人在同一年度不能重复缴费。

第二个校验非常容易被忽略。如果没有唯一约束,前端连续双击保存按钮,就会插两条同一年的缴费记录,后面统计缴费金额直接翻倍。

数据库层面,给payment_record表加个联合唯一索引:

ALTER TABLE payment_record ADD UNIQUE KEY uk_person_year (person_id, year);

代码层面,保存之前也要查一遍:

public void addPayment(PaymentDTO dto) { InsuredPerson person = insuredService.getById(dto.getPersonId()); if (person == null || person.getInsuredStatus() == InsuredStatusEnum.CANCEL.getCode()) { throw new BusinessException("参保人不存在或已退保,无法缴费"); } long count = lambdaQuery() .eq(PaymentRecord::getPersonId, dto.getPersonId()) .eq(PaymentRecord::getYear, dto.getYear()) .count(); if (count > 0) { throw new BusinessException("该参保人本年度已缴费,不能重复录入"); } this.save(...); }

两道防线都做了,才是可靠的实现。数据库唯一约束兜底,代码校验给出友好提示,这是典型的后端开发经验。

4.3 报销金额计算:起付线、比例、封顶线的完整实现

这可以说是整个系统最核心的一个方法,也是答辩时老师最喜欢问的。报销逻辑的正确性直接决定系统能不能用。核心规则用一句话描述:

报销金额 = min( (总医疗费用 - 起付线) × 报销比例 , 封顶线 ),且计算结果不能小于0。

如果总金额没有超过起付线,那么报销金额为0。

为了把规则做灵活,我设计了一个计算方法,先把规则参数从规则表里查出来,再套公式:

public ReimburseResult calculateReimburse(ReimburseParam param) { // 1. 查报销规则表,按报销类型和医院级别匹配 ReimburseRule rule = ruleMapper.selectOne(new LambdaQueryWrapper<ReimburseRule>() .eq(ReimburseRule::getReimburseType, param.getReimburseType()) .eq(ReimburseRule::getOrgLevel, param.getOrgLevel())); if (rule == null) { throw new BusinessException("未匹配到报销规则,请联系管理员配置"); } // 2. 总费用低于起付线,直接返回0 if (param.getTotalAmount().compareTo(rule.getStartLine()) <= 0) { return ReimburseResult.zero(param.getTotalAmount()); } // 3. 计算 (总费用 - 起付线) * 比例 BigDecimal base = param.getTotalAmount().subtract(rule.getStartLine()); BigDecimal reimburseAmount = base.multiply(rule.getRatio()).setScale(2, RoundingMode.HALF_UP); // 4. 超过封顶线,按封顶线报销 if (reimburseAmount.compareTo(rule.getCeilingLine()) > 0) { reimburseAmount = rule.getCeilingLine(); } return new ReimburseResult(param.getTotalAmount(), rule.getStartLine(), rule.getRatio(), rule.getCeilingLine(), reimburseAmount); }

注意几个工程细节:

  • 乘法先乘后除:“总费用 - 起付线”的结果乘以比例,用multiply。如果你先把比例除以100,再用BigDecimal计算,可能出现除不尽的情况,所以最好是规则表里直接存0.7这种小数,而不是存70。
  • 四舍五入:金额保留2位小数,用setScale(2, RoundingMode.HALF_UP),这是银行家公式的实际应用,直接丢掉和进位都可能对不上账。
  • compareTo判断大小:BigDecimal的compareTo才能正确比较数值相同但scale不同的对象,不能用equals,也别转成double比较。

这个计算方法的测试用例也简单,我建议你写几个场景直接跑一遍:总费用低于起付线、等于起付线、正常区间、超过封顶线。四个用例覆盖了所有分支,能帮你在答辩前发现问题。

4.4 报销审核状态机与日志记录

报销单不是申请完直接给钱,而是一套审核流转。最简单的流程是“待审核 -> 初审通过 -> 终审通过 -> 已支付”,任一环节可驳回。

状态流转合法性用和参保状态一样的枚举映射实现:

public enum ReimburseStatusEnum { PENDING(0, "待审核"), FIRST_APPROVED(1, "初审通过"), FINAL_APPROVED(2, "终审通过"), REJECTED(3, "已驳回"), PAID(4, "已支付"); public static final Map<Integer, Set<Integer>> ALLOWED = new HashMap<>() {{ put(0, new HashSet<>(Arrays.asList(1, 3))); put(1, new HashSet<>(Arrays.asList(2, 3))); put(2, new HashSet<>(Collections.singletonList(4))); put(3, new HashSet<>()); put(4, new HashSet<>()); }}; }

每次审核操作,除了改状态,还需要记录审核人、审核时间、审核意见。我建议建一张audit_log表,字段包括业务类型、业务单号、操作人、操作内容、操作时间。前端展示报销详情时,把这条链路拉出来,整个单据的流转过程一目了然,这个对“资料审核类”系统来说是标配,也是管理平台区别于普通CRUD的关键特征。

报销通过后,如果要模拟“拨款”,可以把状态改成已支付,同时写入一条支付时间。如果你还想做得更完整,可以在支付这一步引入一张支付流水表,把业务单和支付单关联起来。

5. 前后端联调与安全细节:最容易翻车的几个地方

5.1 登录鉴权:SpringSecurity + JWT + Vue路由守卫生成一个闭环

管理系统必须做登录,但做了登录就要处理三个问题:密码加密、登录凭证、页面访问控制。

密码加密必须用BCrypt,SpringSecurity自带BCryptPasswordEncoder,不用自己写加密算法。注册用户时对明文密码加密,登录时用passwordEncoder.matches(明文, 密文)校验。

登录成功后签发JWT令牌,后端返回token,前端存到localStorage。axios请求拦截器在每次请求头加Authorization: Bearer token:

service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => Promise.reject(error) )

后端写一个JWT拦截器,解析token并放入SecurityContext,同时从token里拿出用户ID和角色列表。SpringBoot的配置类里放行登录接口和静态资源,其余接口全部要认证。

前端Vue Router的全局前置守卫检查是否存在token,没有token直接跳到登录页:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else { next() } })

如果要做菜单权限,可以在用户登录后从后端接口拉取当前用户的角色和菜单,然后动态生成侧边栏菜单。这个功能叫做“动态路由”,对毕设来说属于进阶加分项,有余力再做。

5.2 金额字段必须用BigDecimal和DECIMAL,这个坑太经典了

前面提到过,这里展开说。float和double在计算机里是二进制浮点数,0.1加0.2可能得到0.30000000000000004。医保系统里每个报销单都是钱,差一分钱都不行。

所以三个地方必须统一:

  • Java实体类中金额字段用BigDecimal,不能是Double或Float。
  • MySQL表中金额字段用DECIMAL(10,2),不能是double类型。
  • 前端提交金额时,注意不要精度丢失。JavaScript的Number在金额较大或小数位多时也会出问题,表单里可以用Number(value),展示时用toFixed(2)。

后端在接收前端传参时,JSON序列化可能会把BigDecimal变成数字,再反序列化时又变回BigDecimal,这块SpringBoot的Jackson默认就能处理。但要注意,如果你用Fastjson,金额字段最好配@JSONField(serialzeFeatures = SerializerFeature.WriteBigDecimalAsPlain),否则可能变成科学计数法。

5.3 日期和跨年度处理:LocalDateTime从入门到防呆

系统的缴费是按年度的,天然涉及年度边界的判断。

  • 缴费记录表里的year字段存的是Integer类型的年份,如2025。不要在Java里用new Date()去解析字符串再取年份,直接用LocalDate.now().getYear()。
  • 报销申请时间、审核时间、支付时间都要精确到时分秒,用LocalDateTime。
  • MyBatis-Plus自动填充创建时间和更新时间,可以实现MetaObjectHandler接口统一处理,不用每个插入方法都手动set。

前端展示日期时,需要注意时区问题。SpringBoot默认序列化LocalDateTime的格式可能带T,前端展示不友好。在application.yml里配一段全局格式:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

再用@JsonFormat做兜底。

5.4 分页查询与索引优化:数据量上来之后不能卡

毕设项目数据量不大,但如果测试时每次查询都要扫全表,体验也会很差。两个最简单的优化点:

  • 身份证号字段必须加索引,因为参保人查询几乎都靠身份证号精确匹配。
  • 缴费记录表、报销申请表的person_id加普通索引,分页查询按创建时间倒序排列。

MyBatis-Plus分页查询的写法:

Page<ReimbursementApply> page = new Page<>(current, size); LambdaQueryWrapper<ReimbursementApply> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StringUtils.hasText(insuredName), ReimbursementApply::getPersonId, insuredId) .eq(status != null, ReimbursementApply::getStatus, status) .orderByDesc(ReimbursementApply::getApplyTime); Page<ReimbursementApply> result = this.page(page, wrapper);

条件构造器里eq(condition, column, value)的重载,condition为false时自动忽略这个条件,这是实现“多条件组合查询”最省事的方式,不用写一堆if拼SQL。

6. 写在最后:怎么让这份源码真正变成“你的”项目

6.1 不要照抄,按这个顺序重构一遍

很多同学拿到的源码直接跑起来就去答辩了,导师一问“报销比例存在哪张表”就愣住。我的建议是拿源码当参考,但一定要自己动手重建一遍,顺序如下:

  1. 先画业务流程图,把参保、缴费、报销三条完整链路画清楚。
  2. 建数据库表,按本文的设计把你自己的字段写出来,不一定要和参考源码完全一致。
  3. 用SpringInitializr新建工程,按“实体 -> Mapper -> Service -> Controller”的顺序逐层写。
  4. 前端用Vue脚手架建工程,先做登录页,再接参保人管理模块,模块顺序从简单到复杂。
  5. 最后再做报销金额计算这个核心方法,单独写测试验证。

这样走完一遍,你对整个代码的掌控力是完全不同的。面试或答辩时,任何一个表、任何一个状态枚举、任何一个计算公式你都能讲清楚来龙去脉。

6.2 答辩时老师最可能追问的问题清单

我根据自己的经验,整理了一份高频提问,提前准备,别到时候现想:

  • 为什么医保报销金额计算要把起付线、比例和封顶线存在规则表里,而不是写死? -> 业务规则会变化,表驱动便于配置,历史单据可以做数据快照。
  • BigDecimal和double的区别? -> 精度问题,涉及金额必须用BigDecimal和DECIMAL。
  • 系统的权限是怎么控制的? -> SpringSecurity + JWT + 前端路由守卫,用户可以配置角色。
  • JWT比Session有什么优点? -> 无状态、支持跨域、适合前后端分离。
  • 如果同一参保人重复提交报销单怎么办? -> 在申请表上做联合唯一约束或先校验再插入。
  • 你系统的数据量能支撑多少? -> 分页查询 + 索引优化,几万到几十万条数据没问题。

这些问题很多并不难,但如果不提前把逻辑理清楚,临场确实会卡壳。尤其报销计算那道题,一定要用上面的代码实例练一遍。

6.3 后续还能扩展的方向

如果你做完主流程还有精力,我建议优先加这三个方向,投入产出比最高:

  1. 居民自助查询端:基于Vue单独做一个页面,居民输入身份证号查询自己的缴费记录和报销进度,技术难度不大,但系统完整度提升一大截。
  2. Excel导入导出:用EasyExcel实现参保人批量导入和报销明细导出,这是真实办公场景里的刚需。
  3. 报表可视化:接入ECharts,把参保人数、年度缴费金额、报销支出按月份画成折线图和柱状图,统计模块就不再只有干巴巴的表格了。

这个项目说到底是给学习和答辩用的,技术不在多,在于每一项技术你都真正理解它为什么这样用。把SpringBoot+Vue+MySQL这条链路吃透,再往后做任何管理系统,换汤不换药,只是业务表不一样而已。

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

Splay树实现详解:旋转、双旋与区间翻转实战

如果你在搜索平衡树资料&#xff0c;肯定会看到这句话&#xff1a;“Splay树&#xff0c;也称伸展树&#xff0c;能在均摊O(log n)时间内完成插入、查找和删除操作。”但真上手写代码时&#xff0c;你会发现这个“翻到根”的动作里全是细节。我从只会背模板到能用它轻松写区间翻…

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

LeetCode刷题项目管理:从二分答案到周赛稳定AC的实战路线

早上打开 LeetCode&#xff0c;习惯性点进 #leetcode# 标签&#xff0c;看到又有人在问"073 爱吃香蕉的狒狒"能不能用二分答案&#xff0c;也有人在复盘周赛430。这画面我太熟悉了。三个月前&#xff0c;我也是从这个标签开始&#xff0c;把热门100题刷了三轮&#x…

作者头像 李华
网站建设 2026/10/3 21:18:00

MyBatis核心原理与实战:从初始化到缓存、TypeHandler与动态SQL

1. 项目概述&#xff1a;MyBatis到底是个什么东西先说结论&#xff1a;MyBatis是一个半自动的ORM框架&#xff0c;它的核心思路是把SQL语句和Java对象映射分开管理&#xff0c;让开发者自己写SQL&#xff0c;而不是由框架帮你自动生成SQL。这一点和Hibernate那种全自动方案有本…

作者头像 李华
网站建设 2026/10/3 21:15:24

ReentrantReadWriteLock 实战:读锁写锁行为、锁降级与死锁避坑指南

之前我有一个内部系统的配置中心&#xff0c;读请求每秒几千次&#xff0c;配置更新却好几分钟才一次。最初图省事&#xff0c;我直接在 get 方法上加了 synchronized&#xff0c;结果每次配置一更新&#xff0c;所有读请求全被堵在门外&#xff0c;高峰期接口响应时间直接飙到…

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

Sonnet 5.5生产接入实战:API调试、VS Code集成与Python同步调用

1. Sonnet 5.5不是“小号Opus”&#xff0c;而是Claude体系里最锋利的工程刀刚看到标题里“跑分贴脸Opus”这句&#xff0c;我第一反应是——别急着关网页&#xff0c;也别急着换模型。我上周在三个不同客户现场同时部署了Sonnet 5.5、Opus 4.6和Haiku 3.5&#xff0c;用同一套…

作者头像 李华
网站建设 2026/10/3 21:10:59

Replit:知识工作的浏览器原生操作系统

1. 这不是一场普通直播&#xff1a;Replit 正在重新定义知识工作的“操作系统”你有没有试过&#xff0c;在浏览器里点几下就跑通一个 Python 爬虫&#xff0c;再拖拽两个组件就搭出带数据库的待办清单 App&#xff0c;最后直接把整个项目链接发给同事——对方点开就能编辑、调…

作者头像 李华