ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3构建城乡居民医保系统:关键实践与部署指南

SpringBoot+Vue3构建城乡居民医保系统:关键实践与部署指南 做城乡居民基本医疗信息管理系统这类Java Web项目说实话不是第一次上手了。这次完整走了一遍SpringBoot2 Vue3 MyBatis-Plus MySQL8.0的组合前端分离架构最后还补齐了全套开发文档和部署手册。如果你正准备做类似选题不管是毕业设计还是公司内的小型医疗业务系统这篇内容能帮你少走不少弯路尤其是MyBatis-Plus分页、MySQL8.0连接配置、Vue3组件化这些最容易踩坑的地方我会把实际排查过程也一并拆开讲。我先说下这个系统的整体轮廓。它本质上是城乡居民基本医疗信息管理系统核心业务围绕参保人员档案、家庭账户管理、医保缴费记录、就医报销审批、定点医疗机构信息这几条主线展开。技术上后端用的是SpringBoot2.7.x配合MyBatis-Plus做数据持久层数据库落在MySQL8.0上前端用的Vue3组合式API加Element Plus组件库状态管理用的Pinia构建工具用的Vite。整套系统下来前端300个文件左右后端90个左右对于中小型业务系统来说是标准的项目体量。下面我把思路拆成几个模块从架构选型、数据库设计到前后端实现细节再到部署上线和常见坑位按实操顺序一个个聊。1. 项目整体架构设计与选型思路1.1 为什么是SpringBoot2 Vue3的前后端分离组合很多人在搭建医疗信息系统的时候会纠结要不要用SpringCloud那一套微服务。我的建议很直接城乡居民医保这类系统业务量一般集中在参保地和经办机构日活跃人数几百到几千这个量级单应用完全可以扛住没必要一上来就拆服务。SpringBoot2单应用加MySQL8.0部署简单排查问题也直观运维成本低这才是符合实际需求的方案。选择SpringBoot2而不是SpringBoot3原因有三点。第一国内生产环境大量的中间件、老版本JDK和私有化部署要求对SpringBoot2的兼容性更好尤其是JDK8场景下完全无缝第二SpringBoot2.7已经是2.x系列里最完善的版本官方维护周期覆盖时间长问题社区沉淀也多搜一个报错基本都能找到答案第三MyBatis-Plus对SpringBoot2支持非常成熟不需要像SpringBoot3那样额外适配。前端选Vue3就更不用多说了。组合式APIsetup语法糖让代码逻辑复用比Vue2的Options API舒服太多配合Vite的冷启动速度开发体验完全是另一档。同一个页面里数据加载、表单校验、分页交互、抽屉弹窗这些业务逻辑可以拆成composable函数整个前端工程的维护性比老项目好一个量级。Element Plus虽然偶尔有版本的小问题但胜在组件全、文档中文清晰做后台管理系统是首选。1.2 MyBatis-Plus vs Spring Data JPA我为什么选MyBatis-Plus网上经常有人问“JPA和MyBatis-Plus到底选哪个”我自己两个都用过Spring Data JPA在ORM自动建表和复杂关联查询上面确实省心但到了医疗报销这类SQL逻辑强的系统JPA的Specification和JPQL写起来很不顺手。比如统计各镇参保人数、汇总当月报销金额、查询跨时间段缴费记录这类多表联查和动态条件拼装用MyBatis-Plus的LambdaQueryWrapper加自定义XML映射文件会清晰得多。MyBatis-Plus最打动我的是三个点。一是条件构造器特别适合医保系统的多条件筛选场景参保人员姓名、身份证号、参保年度、缴费状态这几个条件组合查询事务代码里几行就拼出来不用像原始MyBatis那样手动拼一堆where标签。二是分页插件用起来极简单一个PaginationInnerInterceptor配置到位业务代码里直接传Page对象全程不写物理分页SQL。三是代码生成器可以根据数据库表结构直接生成Entity、Mapper、Service、Controller四层代码像参保人员表和缴费表这种字段多的基础表生成后再手动补业务逻辑效率提高特别明显。JPA在自动建表场景下确实方便但生产环境我很少让框架直接ddl-auto改表结构都是把数据库脚本版本化管理。既然表结构都由SQL脚本控制MyBatis-Plus在查询灵活性和SQL可视化上的优势就更突出。选型这种事没有绝对对错看业务形态定就行。1.3 整体目录结构与团队协作方式这套系统的目录结构我按前后端分两个工程管理medical-insurance-server ├── src/main/java/com/example/insurance │ ├── controller # 控制层只做参数接收和结果封装 │ ├── service # 业务接口 │ ├── service/impl # 业务实现事务控制在这里 │ ├── mapper # MyBatis-Plus的Mapper接口 │ ├── entity # 数据库实体映射 │ ├── dto # 请求参数对象避免实体直接暴露 │ ├── vo # 返回给前端的视图对象 │ ├── config # 配置类分页插件、跨域、Knife4j等 │ ├── common # 统一返回结果、异常处理、工具类 │ └── enums # 缴费状态、报销状态等枚举前端medical-insurance-web是按Vue3标准工程组织medical-insurance-web ├── src/api # 按模块拆分的接口请求文件 ├── src/views # 页面参保人员管理、缴费管理、报销审核、系统管理等 ├── src/components # 公共组件上传、导入、详情抽屉等 ├── src/composables # 组合式函数如useTable、useForm ├── src/router # 路由配置 ├── src/store # Pinia状态管理 ├── src/utils # axios封装、日期处理、权限指令等 └── src/layout # 后台整体布局团队协作上前后端分离最怕接口文档对不上。我没有用传统的Word接口文档而是后端集成Knife4j基于Swagger生成在线接口文档前端直接翻页面看参数示例联调效率提高不少。开发约定是Controller层只做参数绑定和结果封装所有业务判断下放到ServiceImpl层事务边界只出现在ServiceImpl方法上数据权限控制在Service层做杜绝在Controller里写SQL或业务逻辑。2. 核心业务模块拆解与数据库设计2.1 参保人员管理与家庭账户设计城乡居民医保的一个典型特征是以家庭为单位参保跟职工医保的个人账户模式差别很大。所以数据库设计不能简单建一张人员表就完事我拆成了person参保人员表和family_account家庭账户表两张核心表两者通过family_id关联家庭账户是医保缴费拨付和家庭共济的基础单位。人员表的核心字段包括person_id主键雪花IDfamily_id家庭账户ID关联family_accountperson_name姓名id_card身份证号唯一索引同时是医疗报销时最重要的检索字段gender、birth_date基础信息relation_type与户主的关系0-本人1-配偶2-子女3-父母healthy_status健康状态0-正常1-慢性病2-残疾等等insured_status参保状态0-未参保1-正常参保2-暂停3-退保家庭账户表相对简单重点字段有account_no家庭账户编号、owner_name户主姓名、address家庭住址、total_member家庭成员数、account_status。设计的时候我特意把身份证号做成唯一索引因为整个系统的查询、报销、统计都靠身份证号串联这个索引建得值。但要注意身份证号是敏感信息数据库存储时统一加密接口返回时做脱敏处理比如只展示前6位和后4位这个在数据安全层面上很有必要。2.2 缴费记录与报销流程的实现思路缴费模块是城乡居民医保的“资金入口”每年集中征缴期会有大批量记录产生。我建了insurance_payment缴费记录表关键字段包括缴费年度period_year、缴费金额amount、缴费日期、支付方式微信/支付宝/银行代扣/现金、缴费状态待支付/已支付/已退款、以及对应的参保人员ID和家庭账户ID。有两点必须在设计阶段想清楚。第一缴费年度和家庭ID要建联合唯一索引防止同一家庭同一年度重复缴费这比在Java层判断要可靠得多并发时数据库唯一索引是兜底防线。第二缴费金额不能直接用float或double这是老生常谈了金额字段全部用decimal(10,2)Java侧用BigDecimal接收。报销模块是整个系统的核心审核链路我设计的流程是提交申请 → 经办机构初审 → 复审 → 拨付/退回。状态流转用整数状态加一个audit_record审核记录表来维护。报销主表insurance_claim的核心字段就诊类型门诊/住院/慢性病/大病医疗机构ID医疗总费用符合报销范围金额报销比例实际报销金额审核状态0-草稿1-待初审2-初审通过3-复审通过4-已拨付5-驳回这里特别强调一下报销比例和费用计算后端肯定要放一套计算逻辑但永远不要相信前端传入的金额所有金额在后端依据医疗明细重新计算前端只负责展示。实际开发中我还加了明细表claim_detail每一条医疗费用项目都需要单独维护这样审核时能看到费用明细也方便后续统计分析。整个流程的状态机看似简单实际坑就藏在并发上。同一张报销单不能两个人同时审核我采用数据库乐观锁版本号来控制claim表加version字段更新时update ... set version version 1 where claim_id ? and version ?防止审核状态被覆盖。3. 后端核心实现SpringBoot2 MyBatis-Plus实战要点3.1 MyBatis-Plus分页插件与条件构造器十个人用MyBatis-Plus七八个人会遇到分页不生效的问题。原因很简单分页插件没有注册成Bean或者没有指定数据库类型。它不像JPA那样开箱即用分页拦截器需要手动加到Spring容器里。我的配置类是这样写的Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); // 设置最大单页数量防止一次查全部数据 pagination.setMaxLimit(200L); interceptor.addInnerInterceptor(pagination); return interceptor; } }配置好拦截器之后业务代码里分页就非常省事了PagePersonVO page new Page(pageNum, pageSize); LambdaQueryWrapperPerson wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Person::getPersonName, keyword) .eq(idCard ! null, Person::getIdCard, idCard) .orderByDesc(Person::getCreateTime); PagePersonVO result personMapper.selectVoPage(page, wrapper);关键是PaginationInnerInterceptor自动生成LIMIT语句不用手写。selectVoPage是MyBatis-Plus 3.5.3.2之后支持的方法可以直接把实体转成指定VO省掉一层BeanUtils.copy代码干净很多。条件构造器目录里like方法传StringUtils.hasText判断可以避免空字符串查询条件。这个写法在参保人员管理的多条件筛选页面特别常用比如按姓名模糊搜、按身份证精确匹配、按参保状态枚举过滤一条链式写法全搞定。要提醒的是不要把所有查询都堆在构造器里超过三个表关联的复杂报表还是写XML文件里的SQL更清晰。我在统计报表模块就保留了自定义XML碰到了几处需要GROUP BY镇级和年度汇总的复杂查询用LambdaQueryWrapper反而不易读。3.2 逻辑删除、自动填充与枚举状态处理医疗系统里人员信息和缴费记录是不允许物理删除的要么退保、作废、挂起要么做撤销记录。所以我没有用delete操作而是统一采用逻辑删除。MyBatis-Plus支持TableLogic注解全局配置后所有deleteById操作自动转成update这点非常方便。Entity字段加TableLogic TableField(fill FieldFill.INSERT) private Integer deleted;配置文件里设置mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0还有一个高频需求是创建人、创建时间、更新人、更新时间这些字段的自动填充。MyBatis-Plus的MetaObjectHandler拦截器可以统一处理不需要在每个插入更新方法里手动setComponent public class MetaFillHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, createBy, String.class, getUserName()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }跟权限框架集成后createBy字段取当前登录用户再配合TableField(fill FieldFill.INSERT)注解数据库审计字段就全自动维护了。枚举状态字段我采用的是MyBatis-Plus的EnumValue方案。比如报销状态枚举类public enum ClaimStatus implements IEnumInteger { DRAFT(0, 草稿), PENDING_FIRST(1, 待初审), FIRST_PASS(2, 初审通过), FINAL_PASS(3, 复审通过), PAID(4, 已拨付), REJECTED(5, 驳回); EnumValue private final Integer code; private final String desc; }数据库字段存的始终是code值Java层写业务逻辑和前端展示时用desc比到处定义魔法数字强太多。这里有个小技巧把枚举的desc和前端下拉选项对齐后端提供一个/common/enums接口把报销状态枚举直接返回给前端前端就不用硬编码下拉框数据了改字典配置后两端同步非常实用。3.3 事务控制与接口幂等性设计先明确一个难点缴费和报销都涉及资金对事务边界要求很高。我的原则是事务只加在ServiceImpl里用Transactional(rollbackFor Exception.class)。不加rollbackFor的话遇到RuntimeException以外的异常如自定义业务异常可能不回滚这是很多新手容易忽略的细节。还有一点经验要说说。事务不是越大越好比如报销审批操作如果事务里包含调用文件上传接口或生成PDF审批单这种耗时操作长事务会让数据库连接占用时间变长、锁范围变大。我的做法是把“上传附件/生成PDF”放在Controller层或者事务提交后再异步执行事务方法里只操作数据库字段和状态流转保证事务短小精准。接口幂等性也是资金系统的硬要求。用户点“提交报销申请”按钮时网络抖动可能导致重复请求如果没有幂等处理数据库会出现两条一样的报销单。我在前端做了按钮loading禁用只是第一层防线后端还必须兜底。实际用的是Redis的SET NX EX分布式锁public String submitClaim(ClaimDTO dto) { String lockKey claim:submit: dto.getIdCard() : dto.getVisitDate(); boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, Duration.ofSeconds(10)); if (!locked) { throw new BusinessException(请勿重复提交正在处理中); } try { // 业务代码 } finally { redisTemplate.delete(lockKey); } }同时数据库层面我对报销单设计了唯一索引(id_card, visit_date, hospital_id)兜住Redis万一过期或删除不了极端场景下的重复数据。双保险处理下重复提交问题基本没有再出现过。3.4 接口文档与后端统一返回接口设计上遵循统一返回结构所有Controller方法返回ResultT包含code、message、data三个字段。时序图上看起来很简单但好处是前端axios拦截器只要判断code是否为200就能决定是否弹出成功或失败提示不需要每个接口单独处理异常。Data public class ResultT implements Serializable { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.setCode(500); r.setMessage(msg); return r; } }除了统一返回我还全局配置了RestControllerAdvice异常处理器把参数校验异常、业务异常、空指针异常分别转成不同的code。这样前端拿到不同错误码可以做不同提示而不是一律弹“系统繁忙”。接口文档方面集成Knife4j之后每个接口的入参出参一目了然。我把接口按模块分组参保管理、缴费管理、报销管理、统计查询、系统管理文档直接导给前端同学联调用。含文档的项目最终交付时这部分的加分项不小评审看需求文档、数据库文档、接口文档三件套是齐的就行。4. 前端Vue3实现中的关键实践4.1 组合式API的组织方式与状态管理Vue3的setup语法糖用起来很舒坦但代码写多了如果组织混乱照样会变成一坨。我在项目里定了一套规范每个页面拆成“数据加载 表单交互 表格配置 抽屉行为”四个逻辑块对应四个composable文件。比如参保人员管理页面usePersonList负责列表数据和分页逻辑usePersonForm负责新增和编辑表单的状态校验usePersonDetail负责详情抽屉的数据展示模板里面只调用这些组合式函数暴露的响应式变量和方法模板代码行数锐减。以usePersonList为例核心逻辑大致长这样import { ref, onMounted } from vue import { getPersonPage } from /api/person export function usePersonList() { const list ref([]) const total ref(0) const loading ref(false) const queryParams ref({ pageNum: 1, pageSize: 10, personName: , idCard: , insuredStatus: }) async function loadList() { loading.value true try { const res await getPersonPage(queryParams.value) list.value res.data.records total.value res.data.total } finally { loading.value false } } function handleSearch() { queryParams.value.pageNum 1 loadList() } function handleReset() { queryParams.value { pageNum: 1, pageSize: 10, personName: , idCard: , insuredStatus: } loadList() } onMounted(loadList) return { list, total, loading, queryParams, loadList, handleSearch, handleReset } }状态管理我用的Pinia。因为这个系统有经办机构角色和系统管理员角色的区分侧边栏菜单、个人信息、辖区编码这些公共状态需要全局共享。Pinia的setup store写法比Vuex更贴近组合式API一个store里可以定义state、getters、actions而且它对TypeScript的支持比Vuex好后续接TS也顺畅。4.2 表格、表单与打印模板的落地经验后台管理系统最核心的界面就是数据表格加搜索表单。我用Element Plus的el-table配合自定义封装的Pagination组件。这里有个细节要分享不要把el-table的v-loading和分页加载一起写崩异步函数必须放在try/finally中保证loading复位否则第一次接口报错后loading状态卡死整个页面点不动这种问题调试起来特别隐蔽。表单方面城乡居民医保系统的业务表单涉及身份证校验、金额校验、日期范围校验如果全部手写if/else校验逻辑代码量会失控。我用的是Element Plus的el-form加rules校验规则身份证规则用自定义validatorconst validateIdCard (rule, value, callback) { if (!value) { callback() return } const reg /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/ if (!reg.test(value)) { callback(new Error(请输入正确的身份证号)) return } callback() }打印模板是医疗报销系统中一个高频需求要打印缴费凭证、报销结算单。我最初是用window.print()加上CSS的media print控制打印区域只打印结算单区域隐藏导航和按钮。后来在Vue3里封装了一个printArea指令指定DOM节点直接调用比引入第三方print库更可控移除样式干扰也更彻底。如果是复杂报表打印也可以接入像Lodop或浏览器PDF插件做但中小项目里window.print()加CSS媒体查询已经够用。4.3 接口对接和权限控制的坑联调阶段最容易出问题的是日期格式和时间时区。后端返回LocalDateTime序列化默认可能是yyyy-MM-ddTHH:mm:ss前端表格直接展示很难看。我的解决方案是后端统一Jackson配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8前端axios请求里参数序列化也要注意后端RequestBody接收时前端必须设置Content-Type: application/json不能默认用表单格式。我在axios封装里统一设置了header防止个别请求漏传。权限控制层面这个系统分了超级管理员、经办机构管理员、业务审核员三种角色。后端接口用Spring Security加JWT做认证和鉴权前端这边做两件事一是router.beforeEach路由守卫判断有没有token、有没有访问该页面的权限二是按钮级权限比如“审核通过”按钮只有审核员角色看得见。按钮权限我封装了一个v-permission指令指令里判断Pinia里存的角色列表app.directive(permission, { mounted(el, binding) { const permissionRoles binding.value const userRoles useUserStore().roles if (permissionRoles !permissionRoles.some(role userRoles.includes(role))) { el.parentNode el.parentNode.removeChild(el) } } })相比在模板里写一堆v-if判断指令方式清爽很多而且页面模板上不会暴露权限逻辑。5. 环境搭建与部署MySQL8.0从安装到生产5.1 Docker安装MySQL8.0并初始化数据库开发环境里我强烈推荐用Docker跑MySQL8.0省去本机安装的麻烦。一台新机器上用Docker初始化MySQL全程也就一两分钟docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -e MYSQL_DATABASEmedical_insurance \ -e TZAsia/Shanghai \ -v /opt/mysql/data:/var/lib/mysql \ -v /opt/mysql/conf:/etc/mysql/conf.d \ mysql:8.0几个点我吃过亏单独拎出来说。第一时区参数必须指定否则进入容器连接MySQL后时间会比北京时间少8个小时所有CURRENT_TIMESTAMP字段都会错位。第二MySQL8.0默认的认证插件是caching_sha2_password如果用的是旧版JDBC驱动5.x那套会直接报Public Key Retrieval is not allowedSpringBoot2.7.X自带的是mysql-connector-j8.0.x就没这个问题但我还是建议显式在JDBC连接串上加allowPublicKeyRetrievaltrueuseSSLfalse顺手把隐患消掉。初始化数据库脚本的话我按版本号管理SQL文件V1__init_schema.sql、V2__add_claim_detail.sql这种命名配合SpringBoot的spring.sql.init或Flyway管理。生产环境建议上Flyway开发环境下直接用spring.sql.init配置也够用。字符集一定要统一成utf8mb4城乡居民参保人员的姓名里难免有生僻字和特殊符号utf8mb4才能正确存储。5.2 项目打包与Nginx部署细节后端打包用Mavenmvn clean package -DskipTests最终生成一个可执行的medical-insurance-server.jar我生产环境用systemd服务管理而不是nohup java -jar裸跑。systemd的好处是进程崩溃自动重启还能统一看日志。一段简单的/etc/systemd/system/insurance.service配置[Unit] DescriptionMedical Insurance Server Afternetwork.target [Service] Typesimple Userappuser ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /opt/app/medical-insurance-server.jar --spring.profiles.activeprod Restarton-failure RestartSec10 [Install] WantedBymulti-user.target前端构建npm run build产物在dist目录直接放到nginx的html目录。这里有一个必须处理的点Vue3如果用的是history模式路由nginx必须配置try_files否则刷新页面会404server { listen 80; server_name your.domain.com; root /opt/app/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }/api/反向代理到后端8080端口这个配置非常典型。跨域可以在这里直接解决不需要后端再开CorsFilter我建议生产环境统一走nginx代理开发环境才在SpringBoot里配置跨域。6. 常见问题与调试经验速查我把这个项目从开发到部署过程中遇到的高频问题整理成了一张速查表基本都是网上论坛里反复出现的问题。对照着查比重新扒源代码定位快很多。现象根因解决方案分页失效返回全部数据没有配置PaginationInnerInterceptor在配置类中注册MybatisPlusInterceptor并指定DbType.MYSQL插入中文变成问号数据库或连接字符集不是utf8mb4建库指定utf8mb4JDBC连接串加characterEncodingutf8mb4连接MySQL8报Public Key Retrieval is not allowedcaching_sha2_password插件与驱动版本问题连接串加allowPublicKeyRetrievaltrueuseSSLfalse前端时间显示比数据库早8小时Jackson默认时区UTCspring.jackson.time-zoneGMT8刷新页面404Vue3 history路由没有nginx fallbacknginx加try_files $uri $uri/ /index.htmlVue3的reactive数组用index赋值不更新reactive代理对象对索引变更侦测限制用ref定义数组或者使用splice/直接整体替换同一报销单被重复提交接口没有幂等处理Redis分布式锁加数据库唯一索引双保险金额字段出现精度丢失用了double或float存金额数据库decimal(10,2)Java用BigDecimal文件上传后无法访问nginx没配置上传目录别名nginx配置location /files/指向实际存储目录el-table loading卡死异步异常未复位loading在try/finally中保证loading.valuefalse上面表格里有一个值得展开的Vue3问题因为它特别容易踩到。reactive对象如果你整个替换数组比如state.list newArr页面是能更新的但如果做state.list[index] item这种索引赋值在Vue3 reactive代理下可能不触发更新尤其是通过数组下标直接赋值时。最简单的解决方案是直接改用ref声明数组模板里引用会自动解包再通过list.value整体赋值更新触发完全没有心智负担。我后来把项目里的reactive大数组全部换成了ref再没出现过列表数据“改了不变”的诡异问题。排查类的问题给一个定位思路后端接口返回的是不是正常JSON先看network面板里的Response如果JSON正常但前端页面没展示那就是响应式或者数据解析的问题如果接口都404先查nginx代理和后端context-path。这个排查顺序能节省大量时间不要一上来就在代码里打debug先从网络链路开始看。7. 项目文档怎么写才能不拖后腿标题里带【含文档】三个字这块我多说几句。很多开发者的代码确实写得不错但文档一塌糊涂。这个项目的文档我按五件套来组织评审或者接手的人拿到手能顺畅理解需求分析说明、数据库设计文档、接口文档、部署手册、README使用说明。需求分析文档不要写流水账要写清楚角色系统管理员、经办机构管理员、审核员和核心业务流程报销审批流程配一张状态流转文字说明即可。数据库设计文档就按表结构来每张表的字段名、类型、注释尽量写完整索引也列出来这部分在维护阶段价值最大。部署手册的优先级很高。Docker命令、Nginx配置、systemd服务文件、环境变量说明全部写进去按步骤贴命令让一个没接触过这套系统的新同学也能照着部署起来。实测下来一份好的部署手册能在项目交接时省下三个小时的一对一指导时间。README里则放项目结构说明、启动方式、默认账号说明以及已知问题和解决办法。另外代码里尽量多加注释。这不是套话医疗系统业务复杂报销状态流转、家庭账户归属逻辑这种地方不加注释三个月后自己回来看都要掂量半天。8. 实用的经验总结这个系统从前到后完整走了一遍我最深的感受是医保类系统的难度不在技术而在业务建模。把家庭户、参保人员、缴费、报销这几条链路的表结构设计好把状态流转和幂等控制做扎实技术栈选SpringBoot2Vue3MyBatis-PlusMySQL8.0这种成熟组合项目的整体质量就已经很有保障了。最后再分享一个项目收尾的小技巧。交付之前我习惯把MySQL8.0的SQL脚本重新在一个全新的Docker容器里跑一遍确保从头到尾能干净地初始化出所有表再按部署手册走一遍新机器安装流程。这个动作帮我拦截过多次“本地能跑但服务器起不来”的尴尬场景。很多问题并不是代码写错了而是环境初始化步骤缺失。如果你也在做类似系统建议把这个习惯保留下来能省掉后期大量排查时间。
返回列表