ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0疾控系统实践

SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0疾控系统实践 这套疾病防控综合系统我第一次把它完整跑通并交付源码的时候最深的感受是真正拦住项目的并不是所谓的高并发、分布式而是每天都要面对的大量增删改查和动态表单。Java Web 领域里我最终把方案固定为 SpringBoot2 做后端Vue3 做后台管理前端MyBatis-Plus 负责数据库访问层MySQL8.0 作为底层存储源码和配套文档一起移交给后续维护团队。如果你准备做疾控、医疗、公共卫生类的管理系统或者想找个前后端分离项目的完整工程作为参考下面这些内容应该能帮你少走不少弯路。1. 为什么这套组合在疾控系统里比“全家桶”更实际1.1 业务现场给技术选型划下的三条红线我先说结论疾控综合系统的开发难点从来不是技术炫不炫而是交付后的系统能不能在真实业务现场站稳。基层填报人员的浏览器可能还是老版本医院内网环境可能限制外网依赖数据录入页面的字段随时可能因为业务方补充需求增加一列。这些约束给技术选型划下三条红线一是运行环境必须宽容不能逼迫客户升级 JDK 或更换数据库二是开发效率必须高因为疾控业务调整频繁今天建好的表明天可能要加字段三是代码必须容易交接系统往往要交给另一个团队继续维护文档和注释比新框架的噱头更重要。这三条红线其实把选择范围缩小得很明显。SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0 这四个组件在各自的生态里都不是最前沿的但组合起来恰恰能同时满足“环境宽容、开发高效、代码好交接”三个要求。这是我做完整个系统之后回头再看选型时最想强调的一点技术选型不是选最好而是选最适合当前交付环境的一套稳定组合。1.2 SpringBoot2 而不是 SpringBoot3存量环境是最大变量很多朋友会问现在 Spring Boot 3 都出来这么久了为什么还要用 SpringBoot2我在这个项目上的选择很明确看部署环境。疾控类项目经常跑在客户已有的服务器上最常见的组合是 JDK8 加 CentOS7而 Spring Boot 3 的最低要求是 JDK17并且全面转向 Jakarta EE底层运行方式变化较大。强行上 Boot3 意味着要么说服客户升级环境要么在兼容性和维护成本上反复折腾。SpringBoot2 对 JDK8、Tomcat 8.5/9 的支持都非常稳定社区里踩过的坑足够多接手的人遇到问题能很快搜到答案。对一个以“顺利交付并长期维护”为目标的系统来说稳比新更重要。我见过不少项目因为追求新版本最后卡在环境升级的沟通上耗费几周时间。SpringBoot2 在这类系统里并不代表落后它代表的是大规模生产验证过的稳定性。如果你手头没有“必须在 Java 17 上运行”的硬性要求SpringBoot2 仍然是政务、医疗、公共卫生类项目的非常稳妥的选择。1.3 Vue3 组合式 API 对表单密集型页面的价值Vue3 的响应式底层从 Object.defineProperty 换成了 Proxy这让数组元素的新增和删除、对象深层属性的修改都能被可靠追踪。放在疾控业务里最有感的是“病例密接人员登记”这类列表式表单一个病例可能对应十几个密接者每个密接者又有姓名、电话、关系、是否隔离、隔离地点等一堆字段。用 Vue2 的 Options API 写数据和方法散落在 data、methods、computed 里页面一长就很难理清谁改了谁。Vue3 组合式 API 可以在这个场景里把“密接列表”的增删、校验、回显全部封装成 useContactsForm 这样的组合函数页面模板保持干净逻辑还能在不同模块间复用。Element Plus 的表格、表单、日期选择器在 Vue3 下表现也稳定用来搭疾控后台这种“不追求花哨但必须可靠”的界面非常顺手。以前用 Vue2 写这类后台光是搜索条件和表格分页的状态同步就要写一大堆现在用组合式 API 抽一个 useListPage 出来几个页面之间互相复制粘贴的代码量明显减少了。Vue3 的学习成本主要在思维转换一旦熟悉了 ref、reactive、computed 的用法效率和代码可维护性都会上一个台阶。1.4 MyBatis-Plus 与 MySQL8.0不是为了省事而省事MySQL8.0 在这个系统里扮演的角色容易被低估。它默认的 utf8mb4 字符集和更好的排序规则解决了中文和特殊字符存储的显示问题窗口函数让一段趋势统计可以压缩成一条 SQLJSON 类型用来存病例的自定义症状、接触史等非固定结构避免了前期反复改表。MyBatis-Plus 则是我在数据库访问层最顺手的搭档代码生成器能一次把实体类、Mapper、Service 全部生成通用 CRUD 服务基于它对单表查询做了封装让大部分接口在 Service 层就能解决不需要每个模块都重写一套增删改查。它和 MySQL8.0 的配合也很顺滑分页插件、乐观锁插件、逻辑删除都有成熟的接入方式省下来的时间都被我拿去做真正的业务逻辑。很多教程把 MyBatis-Plus 简单等同于“少写代码”实际用下来它的价值不只是少写代码而是让标准增删改查的实现方式统一起来团队里无论谁写出来的接口风格都一致这对后续维护的帮助比省那几行代码大得多。2. 疾病防控系统的模块拆解和数据模型设计2.1 五个模块怎么划分才不会被业务方反复推翻接到“疾病防控综合系统”的需求我第一件事不是画界面原型而是把业务模块的边界跟业务方反复对齐最终落到五大块传染病报告、预检分诊、疫苗接种、统计分析、系统管理。传染病报告管病例填报、审核、流转预检分诊面向医疗机构入口记录发热、腹泻等初筛信息疫苗接种管理疫苗批次、接种记录和不良反应统计分析面向决策人员输出日报、周报和趋势系统管理统一处理用户、角色、菜单和机构层级。五个模块之间不是孤立的比如预检分诊发现可疑病例要能一键生成传染病报告草稿。所以患者主数据我单独建了统一档案表各模块通过 person_id 关联而不是每个模块各自存一份姓名和身份证这样后续做查重和统计才不会乱。模块边界划分得清楚还有一个实际好处每个模块可以由不同的开发并行推进互相之间不会因为数据接口没定好而反复阻塞。2.2 传染病报告表的核心字段设计传染病报告表是整张表里最复杂的一张字段多、状态多、审核链路长。我的设计思路是业务编号和主键分离、核心字段冗余、扩展字段用 JSON。主键用自增 id业务编号单独用 report_no 字符串方便按“地区日期流水号”的规则生成对外展示和追溯都用它。疾病编码用国标 disease_code同时冗余 disease_name避免每次展示都要去字典表 join。report_level 按甲、乙、丙分类存储这个字段直接影响后续的审核流程优先级。report_status 是流程状态机从草稿到待审核、已审核、已驳回、已归档每个状态变化都记录在 report_log 表里保证审计链路完整。发病时间、诊断时间、报告时间三个时间字段分开放统计口径各不相同不能图省事合成一个。索引方面我一般建 disease_code report_status report_time 的联合索引因为审核列表和统计报表的主要查询条件就是“某类疾病、某段时间、某个状态”。其它字段能不加索引就不加索引数一多写入性能会明显下降这是我在几张大表上实测过多次的教训。2.3 疫苗接种表与人员登记表的索引策略疫苗接种表的业务查询集中在两个方向查“某个人打了哪些疫苗”以及查“某个批号的疫苗流向了哪些人”。这两个方向我都会建索引person_id inject_time 联合索引服务第一种查询batch_no 单独索引服务第二种。尤其是 batch_no 的查询如果没有索引一旦接种记录表到了几十万行按批号追踪接种流向就是一个灾难级别的全表扫描。人员登记表的核心是身份证号我建了唯一索引保证同一身份证只能存在一条主档案。但主键不是身份证而用雪花 id因为实际业务里身份证可能变更档案也可能合并用业务主键做物理主键会惹来一堆麻烦。这里想提醒一句唯一索引和逻辑删除字段一起用的时候要特别小心MySQL 的唯一约束会把逻辑删除的数据也纳入判断最常见的处理是给唯一索引加上 deleted 字段或者把已删除记录移到归档表具体要看你们的删除频率等做到那一节我再细说。2.4 窗口函数和 JSON 函数在统计模块的实际用法统计模块是疾控系统里最容易“上线即失败”的部分因为报表查询条件多、范围大、实时性要求高。MySQL8.0 的窗口函数帮我把不少计算从 Java 内存搬回了数据库。比如要算每个地市近十四天的报告数量趋势以及环比变化一条 SQL 里就能用 LAG 拿到前一天的数据SELECT org_name, report_date, report_cnt, LAG(report_cnt, 1) OVER (PARTITION BY org_name ORDER BY report_date) AS prev_cnt, report_cnt - LAG(report_cnt, 1) OVER (PARTITION BY org_name ORDER BY report_date) AS diff_cnt FROM daily_report_stat WHERE report_date DATE_SUB(CURDATE(), INTERVAL 14 DAY) ORDER BY org_name, report_date;这条路子如果放到没有窗口函数的 MySQL5.7需要在业务层把每个地市每天的数据取出来再循环计算上一次的值代码量和出错概率都会上去。JSON 函数则用于扩展字段的筛选比如按症状标签查病例SELECT report_id, person_id FROM report_detail WHERE JSON_CONTAINS(detail_json, JSON_OBJECT(symptom, 发热));这样的查询虽然不能完全替代字段化设计但对疾控系统里大量“不固定采集项”的场景很实用前期建模的时候不必为了每个可能出现的症状去加列。这也是我坚持用 MySQL8.0 的原因之一同样的功能放低版本数据库里要么做不了要么业务层写一坨很脆弱的字符串解析代码。3. 后端基于 MyBatis-Plus 把通用 CRUD 真正用起来3.1 通用 CRUD 封装的边界什么能省什么不能省MyBatis-Plus 提供了 BaseMapper 和 IService让单表 CRUD 变得非常轻但我在项目里没有停留在“每个 Controller 继承一下”的层面而是往上一层又封装了 BaseService。每个业务表的 Service 都从 BaseService 继承BaseService 里提供通用分页、通用详情、通用保存、通用删除。这样做的直接收益是80% 的标准管理接口不用写第二遍新加一个字典表或者小配置表创建实体后直接用现成的 Service接口立刻可用。封装通用 CRUD 的时候要注意边界只提供“按主键操作”的默认实现复杂查询条件由子类自行组合。否则为了通用把 QueryWrapper 直接暴露给前端等于把整张表的结构和数据访问权限都交了出去非常危险。实际开发里我在 BaseService 里写了一套严格内部使用的 LambdaQueryWrapper 构建逻辑外部 Controller 只接收明确的查询对象所有字段过滤都要经过白名单校验这样既保住了开发效率也没把数据库细节漏出去。3.2 疫情预警这种复杂业务为什么不能硬套通用 CRUD传染病报告不只是一个表的增删改查。业务规则里有一条当某个机构当天上报的某类法定传染病达到阈值时系统要自动生成预警记录。这条规则如果用通用 CRUD 写会遇到一个很别扭的问题——通用 Service 不知道“保存报告”之后还要去做“阈值判断并生成预警”。我的做法是把预警逻辑放到独立的事件域服务中报告保存成功后先在同一事务里查出该机构当日同类报告数量和预警规则配置表里的阈值做比较达到阈值就插入预警事件表并更新机构当天的统计冗余字段。整个流程用 Transactional 包住保证报告入库和预警生成要么都成功、要么都失败。这里要特别提醒不要把外部 HTTP 服务调用塞进这个事务否则网络抖动可能导致事务长时间不提交连接池很快会被占满。规则计算如果必须访问外部服务就采用“本地事务提交后发事件再由监听器去调用外部”的方案。我在最初版本里犯过这个错把发送短信通知的接口直接放在预警生成事务里结果短信服务超时整张报告都提交不上去后来改成异步事件才解决。3.3 逻辑删除、乐观锁、自动填充的踩坑记录这三个功能我都用过也都踩过坑。逻辑删除配置很简单实体上 TableLogic全局配置 logic-delete-field查询就会自动带上 deleted 0。但逻辑删除和唯一索引冲突前面已经说过了索引把没删和已删的数据一起算第二次录入同一身份证时 MySQL 会直接报唯一键冲突。我后来是用“唯一键字段 deleted”联合索引解决的删除时把 deleted 从 0 更新成主键值让每条历史记录的唯一性都能独立成立。乐观锁用在疫苗库存扣减上实体加 Version更新时 MyBatis-Plus 会自动带上 version 条件更新行数为 0 就说明有并发冲突业务层统一提示重试。自动填充我用来做 create_time、update_time、create_by、update_by实现 MetaObjectHandler 接口后插入和更新操作会自动填值不用在每个代码分支里手动 set 时间。这三个功能看着都很简单但组合在一起能解决大量重复劳动关键是配置要在项目早期就做好半路再加很容易漏掉历史数据。3.4 后端常被低估的三类问题时区、映射、字段类型第一类是时区。MySQL8.0 的 JDBC 连接串里如果没有 serverTimezoneAsia/Shanghai很可能出现插入时间和查询时间相差 8 小时的问题。我的连接串固定带上一长串参数jdbc:mysql://localhost:3306/dcms?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue第二类是字段映射。MyBatis-Plus 默认开启驼峰转下划线这个通常没问题但要小心实体里用布尔类型映射 MySQL 的 tinyint 时业务含义容易混乱比如“是否隔离”如果用 Boolean 表示查出来的 0/1 到底哪个是是我更喜欢用 Integer 加注释表达。第三类是日期类型。MySQL8.0 默认不允许日期为零值如果用旧版本初始化脚本导入了 0000-00-00 的数据JDBC 读取时会直接报错需要在连接串里设置 zeroDateTimeBehaviorconvertToNull或者在初始化数据时就把日期字段洗干净。4. 前端Vue3 组合式 API 重构复杂后台交互4.1 组合式 API 与选项式 API 的取舍原则Vue3 学习阶段大家都会纠结一个问题组合式 API 到底比选项式 API 好在哪里我的原则是看页面复杂度。简单页面比如只有一张表格和几个搜索条件用 Options API 也很清楚。但疾控系统里大量页面既有列表又有新增、编辑、导入还有弹窗里的子表单用 Options API 就会出现一个问题同一个功能的数据、计算属性、方法分散在不同位置别人接手时要把整个文件看完才知道某个变量从哪里来。组合式 API 允许按功能切分比如列表功能拆成一个 useListPage表单功能拆成一个 useReportForm页面入口只负责组装。这有点像把一个大工具箱按工具类型分成了几个小抽屉拿什么都快。组合式 API 使用中有个常见坑直接解构 reactive 对象会丢失响应性正确做法是用 toRefs 包一下或者保持用 变量.属性 的方式访问。这个坑几乎每个从 Vue2 转 Vue3 的人都会遇到排查起来也容易页面数据修改了但视图不更新先想是不是解构导致响应式丢失。4.2 axios 封装与 JWT 刷新并发请求队列怎么处理前端和后端通过 JWT 做认证。我封装的 request 模块核心是三层拦截请求前统一加 token、响应后统一解包结果、遇到 401 统一处理刷新逻辑。刷新逻辑有个细节容易漏如果页面同时发三个请求三个都返回 401三个都会去调刷新接口刷新接口被并发调用可能失败。我的处理是设置一个全局 isRefreshing 标志第一个 401 发起刷新后面的 401 不重复发起而是把请求压进等待队列刷新完成后按队列依次重放。简化后的代码大概是这样的let isRefreshing false let refreshQueue [] async function handle401(error) { if (!isRefreshing) { isRefreshing true try { const res await refreshToken() setToken(res.data.token) refreshQueue.forEach((cb) cb(res.data.token)) refreshQueue [] return request(error.config) } finally { isRefreshing false } } return new Promise((resolve) { refreshQueue.push((token) { error.config.headers.Authorization Bearer ${token} resolve(request(error.config)) }) }) }这段逻辑写起来不长但如果你跳过队列直接调用项目一上线多人同时操作就会偶尔出现“刷新 token 丢失”的诡异现象。我在本地测试时很难复现因为单个用户操作触发不了并发一到客户现场多个人同时点导出问题就出来了。所以封装请求层时刷新队列这步一定不能省。4.3 一份 Element Plus 动态表单的实现经验“密接人员登记”这类动态表单我用 Element Plus 的表格加行内编辑实现。核心是维护一个 reactive 数组每一行对应一个对象新增时往数组里 push 一个空模板对象并加行校验规则删除时用 splice 去掉对应行最后提交时把整个数组传给后端批量保存接口。实际使用中有个性能和交互上的教训表格行数一旦超过一百行每行都放 input 会让页面明显发卡因为每个输入框都绑定了响应式数据每次键入都要触发整行甚至整表更新。我后来改成“行内只放文本点击编辑按钮才进入该行的可编辑状态”把编辑态从整表收敛到单行流畅度提升非常明显。动态表头也建议用配置驱动把字段配置放到一个数组里表格列用 v-for 渲染这样业务方说要加一列时不用改页面代码结构只改配置就行。这套思路放到疾控系统里很实用因为业务字段的变化频率远比你想象的高页面写死列名的方案遇到需求变更时改动成本很高。4.4 动态路由与按钮级权限控制的落地方式疾控系统的角色非常多省、市、区县、基层机构的权限要求完全不同。前端不能写死菜单而是登录后根据用户角色动态生成。后端返回菜单树和权限编码列表前端用 router.addRoute 把路由动态挂载上去。按钮级权限我用了一个自定义指令 v-permission指令内部判断当前用户的权限码集合里是否包含指定编码不包含就直接把元素移除。这个方案比在每个按钮外面包一层 v-if 干净得多。权限编码要跟后端接口做对应约定比如 report:add、report:audit、vaccine:export所有编码统一放在字典表里维护前端和后端都从同一份文档里取避免编码不一致导致按钮神秘消失。这里有个容易被忽略的细节动态路由在刷新页面时会丢失所以前端必须在应用启动时先从后端拉一次当前用户的路由和权限数据再决定渲染哪些页面。否则就会出现登录后一切正常按 F5 刷新后页面全部空白或者路由跳转到 404 的问题。5. MySQL8.0 部署从 Docker 到 Linux 生产环境的细节5.1 Docker 安装 MySQL8.0 的完整命令与参数解释很多开发环境里最快的方式是直接用 Docker 拉一个 MySQL8.0。我在测试服务器上用的是这样一条命令docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPass \ -e TZAsia/Shanghai \ -v /data/mysql/conf:/etc/mysql/conf.d \ -v /data/mysql/data:/var/lib/mysql \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_0900_ai_ci \ --lower_case_table_names1这里几个参数各有用途TZ 时区如果不设容器默认 UTC 时间和业务日志对不上数据目录挂载到宿主机是必须的不然容器删掉数据全没了lower_case_table_names1 是让表名不区分大小写Windows 开发环境下表和 Linux 生产环境之间拷贝 SQL 才不会因为大小写不一致报错。注意这个参数一定在初始化之前配好MySQL8.0 里它不能在运行后随意更改。字符集用 utf8mb4 是为了兼容中文、生僻字和特殊符号疾控系统里患者姓名、地址这类文本很容易出现这些内容。5.2 caching_sha2_password 认证插件老驱动连不上怎么办MySQL8.0 默认的认证插件是 caching_sha2_password安全性比旧的 mysql_native_password 好但对旧版 JDBC、Navicat 或老版本的数据库驱动不友好典型报错就是 Public Key Retrieval is not allowed。遇到这种问题我的建议是分两步走第一步检查能不能升级驱动或者给连接串加上 allowPublicKeyRetrievaltrueuseSSLfalse这是最简单的方式第二步如果客户环境里的驱动实在没法升级再考虑创建兼容认证方式的用户CREATE USER dcms_app% IDENTIFIED WITH mysql_native_password BY YourPass; GRANT SELECT, INSERT, UPDATE, DELETE ON dcms.* TO dcms_app%; FLUSH PRIVILEGES;这里要注意权限不要给到 GRANT OPTION 或者 ALL疾控数据敏感应用账号能操作业务库的增删改查就够了。生产环境还要定期改密码排查慢查询时也要确认应用没有拿 root 去直连数据库。我见过不少项目把所有账号都建成最高权限后面出问题连审计都无从谈起权限这块一开始就要把好关。5.3 疾控数据备份策略全量加 binlog 增量恢复疾控系统的数据不能只看开发环境能跑还要应对“误删一张表、服务器突然宕机”这类灾难场景。我的备份方案是每日全量加 binlog 增量。先开启 binlogLinux 下 my.cnf 配置加server-id1 log_bin/var/lib/mysql/mysql-bin binlog_formatROW expire_logs_days7然后写一个全量备份脚本名字按日期打好压缩包#!/bin/bash BACKUP_DIR/data/backup DATE$(date %Y%m%d) mysqldump -uroot -pYourPass --single-transaction --flush-logs --master-data2 dcms | gzip $BACKUP_DIR/dcms_$DATE.sql.gz find $BACKUP_DIR -name dcms_*.sql.gz -mtime 7 -delete定时任务加一条0 2 * * * /root/backup_mysql.sh每天凌晨两点执行。--single-transaction 是为了不锁表做一致快照--flush-logs 让 dump 后开启新的 binlog恢复时就能以全量备份文件加上后续 binlog 做增量追平。这个方案对中小规模疾控系统的数据量是绰绰有余的关键是先保证“有备份”这件事真的发生别等出事故才想起来看备份脚本有没有执行。5.4 慢查询排查和几个常用参数调整上线后最常遇到的性能问题不是机器配置不够而是几条 SQL 写得不好。我上线第一步一定会打开慢查询日志slow_query_log1 slow_query_log_file/var/log/mysql/slow.log long_query_time1超过 1 秒的 SQL 会记录到日志里然后一条条拿出来 EXPLAIN看有没有全表扫描、有没有走错索引、有没有在 where 条件里对索引列做函数计算导致索引失效。这类问题用索引就能解决一大半。参数调优我不会一上来就堆一堆配置通常只关注 innodb_buffer_pool_size把它设为可用内存的 60% 左右让 InnoDB 更从容地缓存表和索引。其它参数比如 max_connections、innodb_log_file_size 会在压测之后按实际情况调。与其背一堆调优口诀不如先把慢查询日志和 EXPLAIN 这两件事做扎实我已经见过太多项目死在“参数抄了一堆慢 SQL 却没人看”上。MySQL8.0 的默认配置其实已经足够覆盖大多数业务真正该做的是把日志、监控、备份这些基本功补齐参数调整反而是最后一步。6. 从源码交付到二次开发文档里到底该写什么6.1 交付文档的四层结构每层解决一种人的问题项目交付时带了源码和文档我的文档不是简单的“系统介绍加几张截图”而是按四层来写环境准备说明 JDK、Maven、Node、Docker 的版本和安装方式部署手册写清楚后端打包、前端构建、Nginx 配置和数据库初始化的完整步骤开发手册聚焦工程结构、通用 CRUD 扩展方式、字典表维护、定时任务新增方法运维手册包含备份恢复、日志查看、常见故障处理。四层文档对应四种读者部署的人要看到命令开发的人要看到结构运维的人要看到恢复流程客户只看系统说明。如果全部揉在一份文档里没有一个人能快速找到自己需要的东西。文档里最容易坑人的是版本号。SpringBoot2 的 2.6 和 2.7 之间、Vue3 的 3.2 和 3.4 之间行为差异都不小。如果只写下 SpringBoot2 和 Vue3接手的人拿最新的小版本去构建很可能出现 API 升级不兼容。所以我强制在文档开头放一张版本对应表把 MySQL8.0 的具体小版本也列清楚初始化脚本在哪个目录、默认账号怎么改密码这些全部写成可执行步骤。6.2 源码里的约定和交付前最后的习惯代码里的约定比文档更能减少沟通成本。我的习惯是所有接口统一返回同一个 Result 包装类错误码集中在常量类里异常统一由全局异常处理器捕获业务异常用自定义 BusinessException校验异常由参数校验注解触发其它未知异常兜底记录日志。接口地址统一以模块名开头比如 /api/report、/api/vaccine、/api/statistics。前端 request 封装也要和后端 Result 结构对齐。这样接手团队从后端看接口、从前端看请求都能快速对上。工程上我把配置拆成 application-dev.yml、application-prod.yml 和 application-common.yml敏感信息放到环境变量里读取源代码里只留脱敏后的默认值这套做法对医疗健康类项目的安全审计非常关键。项目最后我会跑一遍代码扫描把明显的空指针风险、未使用的导入、魔法值清理干净。至少在交付那一刻接手的人不用先花一周时间“考古”才能开始写代码。做这套系统过程中我最大的体会是所谓综合系统真正难的不在于某一个刁钻的技术点而在于把每一件看似简单的事情做扎实。技术栈固定成 SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0是为了让团队把精力留给业务逻辑和数据处理而不是每天和框架版本、环境差异缠斗。如果你正在做同类项目建议也先花时间把环境、CRUD 封装、动态表单这三块地基打稳后面的开发节奏会轻松很多。最后再分享一个小习惯每次交付前我会用一条命令把数据库初始化脚本在全新环境里跑一遍确保文档里写的步骤每一步都是真的。这套系统最后能顺利交接这个习惯帮了我大忙。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进