ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MySQL疾病防控系统源码解析与二次开发指南

SpringBoot+Vue+MySQL疾病防控系统源码解析与二次开发指南 疾控中心、医院防保科、社区卫生服务中心……这些机构日常挂在嘴边的“传染病报告管理”“疫苗接种台账”“突发事件处置”落到信息系统里就是一套带着大量表单、审批、统计和权限控制的管理系统。我这两天把一套 SpringBoot Vue MySQL 的疾病防控综合系统源码从头到尾过了一遍可以明确说这是一套能直接跑起来、也能顺手改业务逻辑的完整工程不是那种只展示登录页的框架半成品。整套项目由三块组成SpringBoot 负责后端接口和业务规则Vue 负责页面交互MySQL 负责持久化存储。后台涵盖用户权限、疾病病种字典、传染病报告、疫苗接种管理、应急事件处置记录、综合统计报表等模块基本把疾控基层工作的主链条覆盖了。适合谁看如果你是正在做医疗信息类设计的在校学生或者刚入行的 Java 开发想找一套有业务深度的练手项目再或者单位里需要快速搭一套内部卫生管理系统这套源码都值得你仔细翻一翻。下面我按“理解→改造→运行→排错”这条线把整个项目拆开讲清楚。1. 项目整体设计与业务模块拆解1.1 疾控系统到底在管什么很多人第一次拿到这类系统容易被“疾病防控综合系统”这个名字吓住觉得它无比庞大。其实拆开看核心就是四件事第一件事是“建档”。所有涉及人员和单位的基础档案比如用户、机构、患者基本信息、疫苗接种对象底册都要进系统。这部分是数据底座几乎所有业务都从这里取数。第二件事是“上报”。以传染病报告卡为例临床医生发现疑似病例后填卡卡上有患者姓名、性别、年龄、职业、发病日期、诊断日期、报告单位、病种编码、转归等几十个字段。报告卡提交后由疾控端审核、确认、退卡整个过程要留痕表格字段要跟国家法定传染病监测的大分类对齐。第三件事是“处置”。一旦出现聚集性疫情苗头或突发公共卫生事件系统里要走应急处置流程事件登记、响应分级、流调信息、密接管理、采样检测结果跟踪。这部分更像一个带进度状态的台账系统每一步操作都要记操作人和时间。第四件事是“统计”。疾控工作的日报、周报、月报都依赖数据汇总比如按地区看报告数量、按病种看分布、按年龄段看发病趋势。这套系统里用 SQL 聚合加 ECharts 图表把这部分接起来了这也是我认为整个项目最实用的地方之一。把这四件事理解透了你看源码的时候就不会晕。很多初学者一上来就去翻 Controller 层代码结果被各种业务名词绕晕。我建议你先在数据库里把所有表看一遍再看后端每一个 Service 里处理了什么最后看前端页面在调哪些接口。倒着看速度会快很多。1.2 技术选型背后的取舍逻辑这套系统选 SpringBoot Vue MySQL是医疗信息化项目里非常经典的一套组合不是随便配的。SpringBoot 这几年已经成为 Java 后端的事实标准自带内嵌 Tomcat、自动配置和大量起步依赖可以极大减少配置工作量。疾控这类系统强调稳定、可审计、好维护Java 体系在这一行存量系统里占比非常高。而且很多医院和疾控中心现有的接口对接仍是基于 Java 技术栈的比如通过 HTTP 将院内 HIS 系统的疑似病例数据推送过来后端的对接逻辑用 SpringBoot 做非常顺手。Vue 负责前端则是因为组件化和响应式特性太适合表单密集型系统了。疾控业务里到处是几十个字段的录入页有级联选择、日期区间、批量导入用传统 JSP 模板引擎去维护会非常痛苦。前后端分离之后页面开发和接口调试可以同时进行效率明显更高。数据库选用 MySQL 也很好理解。CDC 业务的数据特征是强关联、强事务、强统计比如“一条报告对应一个患者对应一个病种编码对应一条审核状态”这类关系型数据用 MySQL 的关联查询解决最自然。如果换成 Mongo 这类文档数据库后期做多表统计报表反而要费很多功夫。有人可能会问为什么不干脆用 SpringBoot Thymeleaf 单体现在化我只能说现在做新系统还用模板渲染后续想在 App 端、大屏端复用数据能力会非常被动。前后端分离不是为了炫技而是把数据接口和页面表达解耦这也是这套系统可以快速衍生出一堆子系统的原因。2. SpringBoot 后端核心设计与接口实现2.1 后端工程结构一眼看清系统边界我拿到源码后第一件事是打开后端工程的目录树这里值得你先多看几秒com.example.prevention ├── controller │ ├── AuthController.java │ ├── ReportController.java │ ├── VaccinationController.java │ └── EventController.java ├── service │ ├── ReportService.java │ └── impl ├── mapper │ ├── ReportMapper.java │ └── xml ├── entity │ ├── Report.java │ ├── User.java │ └── Dict.java ├── config │ ├── WebMvcConfig.java │ └── CorsConfig.java └── common ├── Result.java └── exceptions这套结构是典型的 Controller → Service → Mapper 三层。Controller 层尽量只做参数接收和结果包装不要在 Controller 里写复杂业务判断真正的逻辑放在 Service 层因为事务注解、缓存、多表操作都写在这一层才合适Mapper 负责数据访问SQL 原来写在 XML 里还是注解里取决于你的团队习惯这套用得比较多的是 XML。Entity 类与我们常说的数据库表是一一对应的比如Report.java对应report表。common/Result.java是一个统一返回体一般结构包含code、message、data三个字段。这套代码约定所有接口统一返回Result前端一个工具函数就能解包这个习惯很值得保持。有一个细节容易被新手忽略目录里的config/CorsConfig.java几乎是必配的因为前后端分离开发时后端地址是localhost:8080、前端地址是localhost:5173或8081两者端口不同浏览器默认会拦截前端发起的跨域请求。项目能“开箱即跑”很大程度就是靠这个跨域配置兜住了。如果你把项目部署到同一台服务器且做了 Nginx 反代跨域问题会消失但开发阶段必须留着。2.2 核心业务接口从传染病报告到预警提醒系统最重要的接口我按业务价值排个优先级用户登录接口、报告卡新增与审核接口、疫苗记录接口、统计报表接口。以传染病报告为例新增接口的完整流程一般是前端把报告卡几十个字段提交到POST /api/report/add后端先校验必填字段和病种编码是否存在然后组装 Entity 对象给status字段设置为“待审核”再插入数据库返回带新记录 ID 的 Result。这里面真正的业务逻辑在 Service 层Transactional public Result addReport(ReportDTO dto) { // 1. 校验报告卡基本字段 ValidateUtil.requireNotBlank(dto.getCaseNo(), 病例编号不能为空); ValidateUtil.requireNotBlank(dto.getDiseaseCode(), 病种编码不能为空); // 2. 校验病种编码在字典表中存在 Dict disease dictMapper.selectByCode(dto.getDiseaseCode()); if (disease null) { return Result.error(未识别的病种编码); } // 3. 组装实体并落库 Report report new Report(); BeanUtils.copyProperties(dto, report); report.setStatus(PENDING); report.setCreateTime(new Date()); reportMapper.insert(report); // 4. 触发预警规则检查 warningService.checkDiseaseTrend(report.getDiseaseCode(), report.getReportDate()); return Result.ok(report.getId()); }这段代码里最容易忽略的是最后一步“触发预警规则检查”。很多系统只做到了数据录入没有后续动作这不叫“防控”顶多算“登记”。预警实现不复杂通常是用 Spring 自带的Scheduled定时任务每 30 分钟扫描一次当天累计报告数超过设定的阈值比如单病种单日 20 例就向后台生成一条预警记录并通知相关账号。预警阈值不会写死在代码里而是存在数据库配置表里这样业务人员可以自己调整。这种“参数化配置”的思路几乎贯穿整个系统比如事件分级、疫苗库存预警线、导出报表的默认时间范围全部是配置项不是硬编码我在改造时也保留了这一套做法。2.3 登录鉴权与菜单权限疾控系统最难啃的一块这类系统的角色远比普通小博客复杂。有系统管理员、疾控审核人员、医院上报医生、基层接种医生、普通查询用户等。如果每个人都看到全部菜单既不符合操作习惯也存在数据泄露风险。这套系统用的是基于角色的访问控制模型用户表关联角色表角色表关联菜单表登录成功后返回该用户可见的菜单和按钮权限码。后端鉴权早期版本用的是拦截器配合 Token拦截器从请求头拿到 JWT 后验签再把用户信息放回线程变量供后续代码随时取用。public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) { String token req.getHeader(Authorization); if (StringUtils.isBlank(token)) { write401(resp); return false; } try { UserInfo user jwtUtil.parseToken(token); UserContext.set(user); return true; } catch (Exception e) { write401(resp); return false; } }实际开发里最烦的往往不是鉴权本身而是“不同角色能看到哪些数据”这一层。以报告列表为例市级账号要能看到全市报告区县账号只能看到本区县报告单位账号只能看到本单位报告。如果过滤逻辑散落在每个接口里很容易漏掉。正确做法是写一个默认的数据权限拦截或统一的会话服务从当前登录用户绑定的机构层级动态拼接 SQL 条件。我在这套系统里看到它已经用 BaseService 统一处理了大部分数据权限这一点尤其值得借鉴。还有一个小坑JWT 密钥和过期时间不要写在 Java 类里要放进application.yml或者环境变量。这套源码把密钥写在配置文件中已经算合格但生产环境强烈建议继续外移到环境变量否则密钥一旦泄露整个系统的用户令牌都可以被仿造。3. Vue 前端页面与交互设计3.1 前端工程初始化与依赖管理Vue 前端部分我看到的工程默认以 Vue 2 为主UI 组件库用的是 Element UI搭配 Vue Router 和 Vuex或 Pinia 写法。进入前端目录后第一个操作是安装依赖这里有一个高频踩坑点npm install在新环境下经常卡死或者报 ERESOLVE 错误。如果你遇到依赖安装慢先检查镜像源是不是默认 npm 官方源换成国内镜像能快很多npm config set registry https://registry.npmmirror.com npm cache clean --force rm -rf node_modules package-lock.json npm install依赖装不上还有另一类原因是不同 Node 版本下 node-sass 编译失败。现在的 Element UI 老项目大多依赖 node-sass而 node-sass 对 Node 版本极其敏感。解决办法是直接把开发环境固定在 Node 14 到 16 之间或者把 node-sass 替换成 sass。我已经用后一种方案救活过不止一个旧项目替换之后再装依赖基本顺滑。依赖装好之后开发启动命令一般是npm run serve。前端工程里经常能看到一个环境配置文件.env.development核心内容就是后端接口地址VUE_APP_BASE_URLhttp://localhost:8080/api前端所有请求走 Axios 实例统一在请求拦截器里把 Token 加到 Header 上在响应拦截器里统一处理 401 未登录、500 服务异常。这套代码里前端对接口返回的Result都有统一的解包逻辑排错时先定位后端状态码会快很多。3.2 动态路由权限菜单不再是摆设疾控系统要求在登录后根据当前用户的角色动态展示菜单前端不能把所有可访问页面写死在路由表里。实现思路并不神秘后端登录接口返回一个“菜单树”或“路由字符串”数组前端拿到之后用router.addRoute()动态添加同时配合全局路由守卫来判断是否已从服务器拉取过菜单。大致流程是这样用户输入账号密码登录成功收到后端返回的 Token 和用户信息。前端保存 Token 到本地存储同时调用GET /api/auth/menus获取当前用户的可见路由。把拿到的菜单数组转换成 Vue Router 需要的 RouteRecordRaw 结构循环调用addRoute。每次浏览器刷新时由于内存中的动态路由丢失需要重新走一次获取菜单和添加路由的流程。这里有个不容易发现的坑如果你把动态路由和静态路由混合定义可能出现在左侧菜单里看到“当前用户不该看到页面”的情况因为菜单可能是前端全部遍历出来的而你却没有过滤掉无权限路由。比较好的做法是静态路由只放登录页和 404 这种公共页面所有业务页面全部走后端返回的菜单前端只维护页面组件路径到具体视图文件的映射关系。路由守卫中还要注意 Token 过期的问题。如果后端返回 401前端应该清除本地 Token并跳转到登录页。这个逻辑写在 Axios 响应拦截器里比写在每个页面更省事代码统一排查时也不需要逐个页面去翻。3.3 列表、表单和看板数据操作的高频细节前端最常见的是列表页疾病防控系统尤甚。一个报告列表页基本离不开四个要素搜索区、表格区、分页器、操作按钮。Element UI 的表格配合分页组件可以快速搭好但有几个交互细节你要留意。第一个细节是搜索条件与筛选。列表页常驻搜索条件有病种类型、报告状态、报告日期范围。这几个条件传参时后端接收的是DiseaseCode、Status、StartDate、EndDate这一组键值后端做动态 SQL 拼接。这种地方最容易出现“日期范围没有包含结束日”的问题因为 MySQL 的datetime精确到秒如果你传的是 2025-01-01默认是凌晨一天的记录就丢了。正确做法是结束日期传2025-01-01 23:59:59或后端把结束日期加一天再比较。第二个细节是导出功能。疾控系统的报表往往要导 Excel前端用window.open或者隐藏链接很直接但更稳的写法是 Axios 下载 Blob再用URL.createObjectURL触发下载。需要特别注意的是导出接口如果也返回 JSON 错误比如“没有权限”前端要根据 Content-Type 区分是文件流还是报错对象否则会把错误 JSON 存成 xlsx 文件。第三个细节是图表看板。Dashboard 一般用 ECharts 做一个趋势折线图和病种分布饼图。后端返回的数据通常已经是被聚合好的结构比如[{ date: 2025-01-01, total: 23 }, ...]。前端拿数据后要转成 ECharts 需要的 xAxis 数据和 series 数据这一步没什么难度但要注意日期排序否则曲线图会乱跳。后端 SQL 里如果按date分组排序要改成GROUP BY report_date ORDER BY report_date ASC不要依赖前端排序前端排序只能处理已返回的数据遇到分页统计就会出错。表单方面比较大的录入页比如报告卡几十个字段如果全部堆在一个组件里维护难度会直线上升。建议按区域拆组件患者基本信息区、疾病诊断信息区、报告流程信息区。每个子组件内部管理自己的字段校验规则父组件只负责收集所有子组件的值。这套系统里表单校验用得算规范但如果你要做二次开发记得保留 required 提示和信息排列医疗系统对字段完整性要求非常高。另外顺带提一个非核心但常见的扩展点很多单位在后面会要求加健康宣教视频点播对应的是 web 播放 m3u8 片段。如果浏览器不想装插件可以找hls.js这类免安装播放方案但这类能力不是当前系统的主链路建议放到二期扩展。4. MySQL 数据库设计与优化实践4.1 核心表结构疾病、报告、接种怎么建模看系统源码时我习惯先打开 SQL 初始化脚本因为业务逻辑可以骗人表结构骗不了人。这套系统核心表不多但关系设计很到位。以用户与角色为例CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), org_id BIGINT, status TINYINT DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(50) UNIQUE NOT NULL, role_name VARCHAR(50) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sys_user_role ( user_id BIGINT, role_id BIGINT, PRIMARY KEY (user_id, role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这套三表建模是权限管理最普遍的做法不建议改动。还有一个很容易被忽略的设计所有表都带create_time、update_time字段并且默认值用CURRENT_TIMESTAMP。医疗系统做审计时这两个字段是救命稻草我甚至建议你显式记录操作人字段create_by。再看业务核心表report字段设计上有几个值得学习的点病种编码disease_code用varchar(10)而不是字符串大字段因为病种代码是标准编码定长更利于索引。状态status用tinyint或varchar都可以这套里用的是varchar存英文枚举值比如PENDING、CONFIRMED、REJECTED可读性比数字好占的空间也完全可以接受。患者姓名、身份证号、地址这些信息应做脱敏展示处理数据库里存明文没问题接口返回时要根据角色判断是否保留完整字段。疫苗相关表结构类似核心是vaccination_record表包含受种者 ID、疫苗名称、批号、接种日期、接种单位、接种医生。其中批号字段很关键因为后续追溯疫苗不良反应时要从批号反查流向。这里不建外键而是用普通索引加“逻辑外键”的方式因为疾控系统数据量大且需要归档物理外键会影响批量写入和归档灵活性。4.2 索引、排序与统计查询的优化很多“能跑”的系统数据量小的时候没问题等报告记录积累到几十万条就开始卡。这套系统的表结构已经考虑了索引但你在自己改造时还要注意几个优化点。第一是索引要落在常用查询条件上。比如报告表查询高频场景是“按日期范围 状态 病种编码过滤”组合索引建议这样建ALTER TABLE report ADD INDEX idx_date_status_disease (report_date, status, disease_code);注意组合索引的最左前缀原则第一个字段尽量是筛选度最高的或者固定都会出现的字段。report_date作为第一个 index 字段是合理的因为几乎每次列表查询都会带上日期范围。第二是排序问题。列表页排序如果字段不在索引上MySQL 就会使用 filesort数据量一大性能迅速下降。像ORDER BY report_date DESC这样的排序如果你的索引首字段刚好是report_date那排序就不用额外消耗但如果排序字段变成update_time建议单独给它建一个索引。第三是中文排序的坑。MySQL 默认排序规则是utf8mb4_general_ci中文会按拼音排序如果不希望这样可以在建表时指定utf8mb4_bin或者用ORDER BY CONVERT(field USING gbk)。在疾病字典排序里有时确实需要按拼音有时需要按编码你要先和业务方确认需求不要凭直觉定。第四是统计查询。日报表汇总最容易出现的性能问题是“循环查库”比如按地区循环查询每个地区的报告数。那种写法一旦地区多就慢。正确做法是用一条 SQLSELECT org_id, COUNT(*) AS report_cnt, disease_code FROM report WHERE report_date BETWEEN #{start} AND #{end} GROUP BY org_id, disease_code ORDER BY report_cnt DESC;然后前端再做二维渲染。这套系统在报表模块用的就是这种聚合查询配合结果集在内存里的二次组装实测下来接口响应时间可以接受。4.3 数据库连接相关的高频坑数据库层最容易让新手当场崩溃的是连接不到 MySQL。症状千奇百怪但根源就那么几个。拿这套系统来说application.yml数据库连接串一般长这样spring: datasource: url: jdbc:mysql://localhost:3306/prevention?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver我见过太多人因为少了useSSLfalse折腾半天。MySQL 8.0 默认 SSL 配置和连接器版本不一致时会报“SSL connection error”。第一次跑这套源码这个参数直接抄上最省心。另一个高频报错是UnknownInitialCharacterSet类似问题本质是 MySQL 连接器不识别服务器返回的字符集最好方案就是把数据库、表、连接 URL 的字符集全部统一为utf8mb4。还有时区问题。MySQL 8.0 默认时区可能与系统不一样连接报错serverTimezone是家常便饭。serverTimezoneAsia/Shanghai这个参数要写在 URL 里单靠改 MySQL 的time_zone变量不保险两边一起改最安心。5. 从零到可直接运行环境搭建与实体启动全流程5.1 开发环境版本搭配建议先把结论放前面这是我跑了多套类似源码后才定下的稳妥组合组件推荐版本说明JDK1.88u202SpringBoot 2.x 的下限且生态资料最全SpringBoot2.5 ~ 2.7不要一上来追 3.x很多旧教程代码跑不起来Maven3.6.3对 JDK8 兼容性好稳定为主Node.js14 ~ 16Vue 2 工程搭建时最省心MySQL5.7 或 8.0都可以推荐 8.0注意连接串参数IDEIDEA 2021只要能配 SpringBoot 启动项就行为什么特意提 SpringBoot 版本因为我见过有人直接把 2.7 改成 3.2结果包名从javax.servlet改成jakarta.servlet大量代码报红最后项目根本编译不过。新版本不是不好但为了一套源码快速跑起来你不需要承担迁移成本。老版本遇到的问题网上几乎都有现成答案这比追新重要得多。5.2 数据库初始化与后端启动完整步骤第一步准备 MySQL 并确认能登录。如果你还没有 MySQLWindows 上最简单的安装方式就是下载安装包一路下一步用 8.0 版本问题不大。如果之前装过 5.7 的压缩包版注意配置my.ini和data目录初始化的坑那块单独开一篇都不够写。这套源码用 8.0 也完全兼容不需要纠结。第二步创建数据库并导入脚本。一般源码目录下会有sql/prevention.sql或类似的初始化脚本mysql -u root -p CREATE DATABASE prevention DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE prevention; SOURCE /path/to/prevention.sql;导入成功后可以用SHOW TABLES;检查表是否齐全。如果发现表数量不对多半是 SQL 脚本里有语句报错被中断了回头逐行看错误信息。第三步修改application.yml里的数据库账号密码以及文件上传路径。这两种配置最容易漏漏了要么连不上库要么附件的接口报“根路径不存在”。第四步启动后端。在 IDEA 里直接 Main 方法运行即可。如果你想改启动端口在application.yml中设置server: port: 8080如果你是命令行方式启动先 Maven 打包mvn clean package -DskipTests java -jar target/prevention.jar看到类似Tomcat started on port(s): 8080的输出就算后端成功。此时你可以先访问一个无需鉴权的接口看看能否返回 JSON。第五步启动前端。进入前端目录后npm install npm run serve控制台出现App running at Localhost:端口后浏览器打开地址用默认账号登录即可进入系统。整套流程顺利的话半小时内一定能看到登录页面。5.3 前端打包合并进 SpringBoot 的两种姿势开发环境是前后端分离跑但很多单位并没有独立的 Nginx 或 Node 环境希望一个 Java 进程搞定全部。这就要把前端构建产物放进 SpringBoot 里一起发布。如果你搜过“vue打包放进springboot中”相关的资料会发现做法五花八门但核心其实是两种。第一种做法是哈希路由模式加静态资源复制。前端执行npm run build后把生成的dist目录整体复制到后端src/main/resources/static下。由于 Vue Router 用的是hash模式URL 带#符号浏览器刷新时不会向服务器发实际路径请求所以直接双击 index 也能跳转。这种方案最简单但 URL 美观性一般地址栏总是带个#。第二种做法是 history 模式加路由回退。前端路由写成createWebHistory()URL 干净但刷新页面时服务器收到的是真实路径比如/report/list后端不认识这个路径就会返回 404。解决办法是让后端把不属于/api的路径统一转发到/index.htmlOverride public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{spring:^[a-zA-Z0-9\\-\\.]}) .setViewName(forward:/index.html); }这个做法在项目里验证过是可以跑的但要注意别把/api/**也转发进去否则前端请求后端接口时也会被转去 index造成各种奇奇怪怪的 404。实际上我在大多数内部项目中更推荐 Nginx 部署前端静态资源交给 Nginx后端 API 通过反向代理转发动静分离维护也清晰。但如果就是图省事想维持“一套 Java 程序跑完整个系统”上面第二种方式是最贴近需求的。另外提醒一个合并部署后的静态资源缓存问题。前端每次发版本后如果不更新版本号浏览器缓存旧的 index.html用户会看到旧页面。要么在前端构建时给资源加 hash要么在后端接口统一加上 no-cache 响应头。6. 常见问题与排查技巧实录6.1 后端启动阶段的高频报错现象最常见原因解决办法8080 端口被占用本地已启动其他服务改server.port8081或找到进程 kill 掉启动直接失败提示找不到主类Maven 依赖没下载完或没 clean执行mvn clean package -DskipTests后重跑Maven 依赖下载超慢或直接超时使用了官方中央仓库在settings.xml里配置阿里云镜像启动后接口报 404Controller 没扫到包或 XML 没编译检查主类上的SpringBootApplication扫描包路径提示mapper绑定异常Mapper 接口与 XML 命名空间不对检查 XML 的 namespace 是否与接口全限定名一致报ClassNotFoundException: javax.*用了 SpringBoot 3.x 却按 2.x 代码改退回 SpringBoot 2.5或把 javax 改成 jakarta这里说一个值得反复检查的坑很多源码启动没问题但接口一调用就 500日志显示“Invalid bound statement”。原因是mapper/**/*.xml文件没被 Maven 打进 classpath 里。解决方式是在pom.xml的build里添加resources配置把 XML 目录包含进去。resources resource directorysrc/main/java/directory includes include**/xml/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources这种问题只在打包部署时出现IDE 直接运行时由于编译输出目录结构不同反而少见所以更容易迷惑人。6.2 前端联调阶段的高频报错前端联调最大的拦路虎是跨域。现象是后端接口在浏览器 DevTools 里直接访问能通但前端页面上请求就报blocked by CORS policy。解决办法有两种。一种是后端配置全局跨域比如在 CorsConfig 里允许所有来源和所有常用方法开发阶段可以先这样。另一种是前端配代理在vue.config.js里写module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };这样前端页面里写的/api/report/list会被代理转发到后端的8080从浏览器视角看起来是同源就没有跨域问题。要注意的是代理只对开发环境生效生产环境还是要靠 Nginx 或后端 CORS。还有一类问题是登录后页面能打开但列表数据一直加载失败状态码 401。常见原因有两个一是前端 Axios 拦截器里没有把你的token放到错误场景中请求头里根本没有任何凭证二是请求带了 token但后端解析失败比如密钥两边配置不一致。前端刷新页面时路由白屏也经常被问。如果你用的是路由 history 模式且没有做回退浏览器直接访问/report/list就会 404这不是代码报错是服务器没有针对前端路由做处理。解决方法要么切回 hash 模式要么按上一章说的加回退转发。另外npm install之后出现sass或node-sass相关的启动错误多半是 Node 版本问题。建议先确认node -v如果版本超过 17直接降到 16 往往最快解决。6.3 数据库部署相关的疑难杂症数据库这块最经典的十分钟难题就是“本地连不上 MySQL”。按照我的排查顺序比看一堆日志更有效第一步确认服务是否启动。Windows 上win r打开服务列表找 MySQL 服务如果没启动就启动。如果启动失败去看 MySQL 错误日志多半是my.ini里的路径不匹配或权限问题。第二步确认账号能登录。命令行mysql -u root -p如果报Access denied要么密码记错了要么 root 的auth_plugin有问题。MySQL 8.0 默认缓存密码插件是 caching_sha2_password有些比较老的连接器版本不支持可以改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;第三步确认远程是否能连。如果项目部署在另一台机器上而你想本地连过去调试需要给 MySQL 开远程权限CREATE USER app_user% IDENTIFIED BY StrongPassword123; GRANT ALL PRIVILEGES ON prevention.* TO app_user%; FLUSH PRIVILEGES;还要检查系统防火墙是否放行 3306 端口以及 MySQL 是否绑定在127.0.0.1上。如果bind-address127.0.0.1远程永远连不上要把它改成0.0.0.0或者注释掉。第四步检查连接串。报Public Key Retrieval is not allowed是 MySQL 8.0 和较新连接器的经典组合问题解决办法是连接串加allowPublicKeyRetrievaltrue。报SSL connection error就加useSSLfalse。报时区问题就加serverTimezoneAsia/Shanghai。6.4 维护这套系统半年后的几点体会源码这东西故事永远比代码本身多。我拿到这套系统后先做的一件事不是急着扩展功能而是把数据库里所有字典表先看了一遍。任何医疗业务系统字典表的完备程度基本决定了后续扩展的天花板。病种分类不全、状态枚举不统一后面改接口时就会反复劝说业务方“这个是历史遗留”。所以如果你要二次开发第一优先去补全字典。其次是不要把太多逻辑堆在 Controller 里。我见过不少人把几十行代码直接写在接口里一时跑得快后面做单元测试、加缓存、换数据源时就开始哭。这套系统分层明确你要学着继续保持这个纪律Controller 只收参Service 管事务Mapper 管数据。在权限和数据的匹配上我也踩过坑。曾给一个区级账号加了一个“全市导出”的按钮结果那个账号在列表里看到的数据本来就是区级过滤后的导出接口又拼了一次完整查询数据直接越权流出。那次之后我给导出接口单独加了一层与页面列表完全相同的权限校验逻辑并且每次改动都要重新核对数据权限 SQL。安全层面的东西再谨慎都不过分。关于系统本身如果你想把它从校园作品往生产级方向推有几件事是躲不开的操作日志要单独建表记录99% 的医疗审计都会查这里密码不要明文存用 BCrypt 加密文件上传要限制扩展名不能给人传 jsp 或者 php 的机会数据库备份要做定时策略。这些不是锦上添花而是底线。最后再补一个很实际的技巧这套系统在演示或互转环境时你不得不考虑初始化和测试数据的问题。初始化脚本里的密码字段多数是对称加密或哈希后的值为了防止交接时的人改不动可以自己写一个注册接口或者用 SQL 直接更新用户表密码。我在测试环境里常用一段简单的 SpringBoot 启动时初始化逻辑判断用户表为空时自动建一个管理员账号这样任何环境拉到最新代码都能秒进系统比拿着加密串到处问人要省事得多。我用这套系统改过一个社区化的慢病随访模块前后用了两三天基础框架没动只是在患者台账、随访计划、医护人员排班之间加了几张业务表后端套用了旧的分页和导出逻辑前端复用了一批列表页组件。你会发现真正成熟的源码最有价值的不是一键运行的炫技而是你能在它的分层结构里找到自己放代码的位置改起来不手忙脚乱。这就是它所承载的经验。
返回列表