接手过不少疾控相关的小型业务系统,也看过市面上很多打着“企业级”旗号的疾病防控管理系统源码。说实话,大多数所谓“完整版”项目,要么是简单CRUD拼凑,要么是界面老旧、代码混乱,很难直接用到真实业务里。但这套基于SpringBoot+Vue+MyBatis+MySQL的疾病防控综合管理系统源码,算是我见过比较规整、能真正落地的一套。
项目采用经典前后端分离架构,后端SpringBoot负责业务逻辑与接口,前端Vue2+Vuex+ElementUI搭建管理界面,MyBatis作为数据持久层框架,MySQL存储业务数据。整体设计覆盖了疾病预防控制中心或企业卫生管理部门的日常核心诉求:传染病报告管理、疫苗接种记录、重点人群健康监测、物资库存管理、系统用户与权限管理。如果你正好需要一套能直接二次开发的疾控管理底座,这篇文章我会把这个系统的核心设计拆开揉碎,从架构选型到表结构设计,从接口实现到前端权限控制,再到部署时的常见坑,一次性讲透。
1. 整体架构设计与技术选型思路
很多人拿到源码第一件事就是跑起来看效果,但这样很容易忽略项目最值钱的部分——架构设计。这套系统的技术选型不是随意堆砌的,每一层都有明确的目的性。
1.1 前后端分离:为什么不是传统JSP或Thymeleaf
早期疾控类系统很多用JSP+Servlet或者SpringMVC+Thymeleaf,开发快,但前后端耦合严重。这套系统选择Vue+SpringBoot彻底分离,核心考虑有两点。
第一是并发与部署的灵活性。后端只提供JSON接口,前端静态资源可以独立部署到Nginx,也可以打包后扔进SpringBoot的static目录统一管理。企业内网部署时,经常需要把前端包和后端jar合并成一个部署包,SpringBoot天然支持这种做法,运维成本极低。
第二是团队协作与二次开发。疾控业务往往需要频繁调整表单字段和报表页面,前后端分离后,前端人员改页面不影响后端逻辑,后端调整接口也不怕页面崩。类似疫苗接种登记这类高频交互页面,Vue的组件化开发能明显提升复用率,比如人员信息表单组件可以在多个模块复用。
这套源码的前端是基于Vue2+Vue Router+Vuex+ElementUI搭建的。Vue2稳定性好,ElementUI组件库成熟,对疾控这类以表格、表单、弹窗为主的管理系统来说,开发效率非常高。Vue Router负责动态路由注册,Vuex负责全局用户信息和权限状态管理,接口层统一封装了axios请求,附带token拦截与错误码统一处理。
1.2 后端分层:Controller-Service-Mapper的经典分工
后端Java工程采用标准的三层架构,Controller层只做参数接收和结果封装,Service层承载业务逻辑,Mapper层通过MyBatis操作数据库。这种分层的最大价值在于,当流感暴发需要临时增加批量上报接口时,只需要在Controller加一个入口,在Service复用已有的业务方法,在Mapper追加一条SQL即可,改动范围清晰可控。
项目包名规划也很规整,controller、service、mapper、entity、config、common、utils每个包职责单一。common包下放了统一返回结果类Result和全局异常处理器,config包下配置了CORS跨域、拦截器、MyBatis插件等。
这里值得一提的是MyBatis的使用方式。项目采用XML文件与注解混用的模式,复杂动态SQL全部写在XML里,简单查询用注解。比如疫情数据统计报表的SQL,涉及多表join、子查询、时间分组,用XML的<select>标签配合<where>、<if>动态条件,比Java代码里拼接SQL要清晰得多。MyBatis的#{}预编译机制也有效防止了SQL注入,这在疾控系统里尤为重要,因为涉及的居民健康信息属于敏感数据。
1.3 MySQL存储引擎与字符集选择
数据库这块,源码默认配置的是MySQL 5.7+,存储引擎统一使用InnoDB,字符集utf8mb4。为什么强调utf8mb4,因为疾控系统的人员姓名、住址、备注字段经常会出现生僻字或特殊符号,utf8mb4是MySQL中唯一完整支持Unicode的字符集,能存下所有生僻字和emoji。排序规则建议用utf8mb4_general_ci,性能略优于utf8mb4_unicode_ci,对中文检索影响不大。
InnoDB的理由也简单:支持事务、支持行级锁、支持外键。疾控数据上报过程中经常需要同时更新多个表,比如登记一个传染病报告卡,要同时插入报告主表、追踪记录表、审核日志表,没有事务保护很容易出现数据不一致。
2. 核心业务模块与数据库设计详解
2.1 疾病防控业务的六大核心模块
这个系统在功能模块设计上,基本还原了疾控中心或企业卫生管理的日常业务流。我梳理后认为有六个模块是核心中的核心。
第一个是传染病报告管理。支持按病种编码录入、审核、查重、订正、删除(仅限错误报告),整个流程模拟了真实疾控的报卡流程。录入页面带病种下拉联动,选“甲类”会自动提示上报时限和处置要求。列表页支持多条件筛选,包括报告日期范围、病种、地区、审核状态。
第二个是疫苗接种管理。这块功能覆盖了从疫苗入库、库存分配到接种记录登记的全链条。疫苗批次管理细致到生产厂家、批号、有效期。接种记录与重点人群模块联动,可以直接从居民档案一键跳转登记。
第三个是重点人群健康监测。系统内置了高血压、糖尿病、严重精神障碍、肺结核等多类重点管理人群的随访模板,随访记录支持按时间轴查看,可以直观看到每次随访的各项指标变化趋势。
第四个是应急物资管理。考虑了物资的入库、出库、盘点、效期预警。防护服、口罩、消毒液、试剂盒这些应急物资需要分类分仓管理,系统支持独立的仓库维度,预警阈值可以按物资类别独立设定。库存低于阈值会自动生成采购建议单。
第五个是系统用户与权限管理。采用RBAC模型,用户-角色-菜单三级权限架构。市级账号能看到全市数据,区县级只能看到本区县。菜单权限精细到按钮级别,比如“删除报告”按钮只有超级管理员或特定角色才能看到。
第六个是综合统计报表。这块是基于业务数据的二次加工,报表全部通过MyBatis动态SQL实现。首页仪表盘展示今日新增报告、待审核数量、库存预警数量等核心指标。趋势分析用ECharts绘制,比如近30天传染病发病趋势、疫苗接种完成率。
2.2 数据库表结构设计要点
源码的数据库脚本是一个完整的SQL文件,包含全部建表语句和初始数据。我梳理了核心表的关联关系,大概可以分成这么几块。
用户权限块有sys_user、sys_role、sys_menu、sys_user_role、sys_role_menu五张表,这是标准的RBAC五表设计。sys_user表的密码字段存储的是BCrypt加密后的密文,登录校验用BCryptPasswordEncoder。这里提醒一下,如果你拿到源码发现初始密码登录不上,大概率是因为数据库脚本里的密文与你尝试的明文不匹配。
传染病报告块以tb_report_case为核心表,字段包含报告卡编号、患者姓名、身份证号、病种编码、报告类型(疑似/确诊)、报告单位、报告医生、审核状态、审核意见等,外加create_time、update_time。追踪记录单独拆成tb_follow_up表,每一条报告卡可以挂多条追踪记录。
疫苗接种块核心表是tb_vaccination_record,关联了疫苗批次表tb_vaccine_batch和接种对象表tb_resident。一个重要的细节是,每次接种记录都会冗余疫苗名称、生产企业、批号等字段。尽管理论上应该通过联表查询获取,但真实业务中接种记录生成后疫苗批次信息可能变动,冗余快照能保证历史记录不可篡改。
重点人群块则围绕tb_resident居民档案表展开,关联tb_follow_up_record随访记录表。居民档案表包含了姓名、性别、出生日期、身份证号、联系电话、户籍地址、现住址、慢病类型等四十多个字段。随访记录表则记录了随访方式、随访日期、症状、体征、用药情况、遵医行为、下次随访日期等。
物资管理块包含tb_material、tb_material_stock、tb_material_storage_log三张表,分别存物资基础信息、各仓库库存、出入库流水。物资入库和出库都会写流水表,通过流水表可以追溯到每一次库存变动的操作人、时间和事由。
2.3 关键表结构SQL解读
下面我从源码中摘一段核心建表语句,以传染病报告主表为例,逐块解释每个字段的设计意图。
CREATE TABLE `tb_report_case` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `case_no` varchar(32) NOT NULL COMMENT '报告卡编号', `patient_name` varchar(64) NOT NULL COMMENT '患者姓名', `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', `disease_code` varchar(20) NOT NULL COMMENT '病种编码', `disease_name` varchar(64) NOT NULL COMMENT '病种名称', `report_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '报告类型:1疑似 2确诊', `report_unit` varchar(128) DEFAULT NULL COMMENT '报告单位', `report_doctor` varchar(64) DEFAULT NULL COMMENT '报告医生', `report_date` datetime NOT NULL COMMENT '报告日期', `audit_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '审核状态:0待审核 1通过 2驳回', `audit_opinion` varchar(512) DEFAULT NULL COMMENT '审核意见', `audit_user` varchar(64) DEFAULT NULL COMMENT '审核人', `audit_time` datetime DEFAULT NULL COMMENT '审核时间', `del_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记:0未删除 1已删除', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_case_no` (`case_no`), KEY `idx_disease_code` (`disease_code`), KEY `idx_report_date` (`report_date`), KEY `idx_audit_status` (`audit_status`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='传染病报告卡表';这里面有几个设计细节值得留意。
case_no报告卡编号是唯一键,实际业务中这个编号往往由系统按照日期加流水自动生成,比如格式为GB20240115001。用唯一键约束可以防止重复编号插入,从根源上杜绝一卡多报。
时间字段统一用datetime而不是timestamp,是因为疾控数据归档需要长期保存,timestamp只能存到2038年,虽然现在看着很远,但这套系统如果持续运行十年以上,早晚会踩坑。
动态更新字段update_time使用了ON UPDATE CURRENT_TIMESTAMP,这样只要行记录发生任何修改,MySQL会自动刷新更新时间,不用在业务代码里手动set,减少遗漏。
idx_report_date和idx_audit_status这两个索引是报表查询的关键。统计某段时间内各审核状态的报告数量,走的就是这两列组成的复合查询。
2.4 数据库设计避坑心得
这类管理系统在数据库层面最容易出现的问题,是过度依赖逻辑删除而放弃物理删除。这套源码用del_flag字段做软删除,列表查询统一加del_flag = 0条件。这个设计本身没问题,但二次开发时很容易忘记在SQL里过滤,导致已删除数据反复出现。我的建议是,如果团队对MyBatis不熟,可以把del_flag = 0直接写在MyBatis的全局配置里,通过自定义拦截器统一追加,不需要每个SQL手工加。
另一个容易踩坑的地方是大字段查询。人员档案表里如果有照片或体检报告附件,字段类型可能用了longtext。前端列表页如果直接SELECT *,每行都会把这个大字段加载到内存,数据量上来后会非常慢。正确做法是列表查询只查必要字段,详情接口再全量查。源码里大部分列表SQL都是明确定义了查询列的,这点做得比较好。
3. SpringBoot+Vue+MyBatis核心实现拆解
3.1 后端接口设计与统一返回结构
这套系统的后端接口设计属于非常标准的RESTful风格,路径规划以模块为根,比如/api/report、/api/vaccine、/api/material。每个控制器继承统一的BaseController,提供了获取当前登录用户、分页参数解析等公共方法。
所有接口返回统一使用Result对象封装,结构是code、message、data三个字段。code=200表示成功,code=500表示服务器异常,code=401表示token失效或未登录。前端axios响应拦截器里判断code,统一弹错误提示。这套机制能够极大简化前端错误处理逻辑,不需要每个接口单独判断状态。
全局异常处理器是整个后端稳定性的关键。源码用@RestControllerAdvice定义了两个核心异常处理方法:一个是处理业务异常的BusinessException,比如库存不足、审核状态不允许修改,返回业务错误码和信息;另一个是兜底处理系统异常的Exception捕获,统一记录日志并返回友好提示,避免前端看到Tomcat的500堆栈页。我见过很多项目没有这层处理,一旦SQL异常直接把堆栈抛给前端,既不安全也不友好。
3.2 登录认证与权限控制链路
权限控制这块要重点讲一下,因为它是整个系统的安全基座。
后端登录接口验证用户名密码,使用BCryptPasswordEncoder验证密文。验证通过后用JwtUtil生成token,token里封装了用户ID、用户名、角色标识,并设置了过期时间,默认配置是12小时。前端拿到token后存入Vuex和localStorage,axios请求拦截器会从localStorage取出token,放到请求头的Authorization字段。
每次请求到达后端时,首先经过拦截器。拦截器从请求头解析token,校验签名和有效期,解析出用户信息放到ThreadLocal中,供后续Service层直接获取当前操作人。对于权限,后端在每个Controller方法上用@RequiresPermissions("system:report:audit")这类注解标记所需权限码。AOP切面会拦截带注解的方法,从数据库中查出当前用户的权限码集合,判断是否包含所需权限。不通过则抛出AccessDeniedException,由全局异常处理器转换为403响应码。
前端层面的权限控制同样重要。登录成功后,后端会返回完整的菜单树和按钮权限码集合。前端用store.commit('setPermissions', data)把权限存入Vuex,然后通过v-permission自定义指令控制按钮显隐。动态路由是这套权限控制里比较高级的玩法:路由表先只注册公共路由(登录页、404页),菜单权限接口返回的菜单数据在路由守卫里动态匹配前端预先定义好的组件映射,然后用router.addRoutes动态挂载。这样不同角色登录后,看到的菜单和对应路由完全不同。
3.3 MyBatis动态SQL编写与参数传递
既然标题里点名了MyBatis,这块需要单独拉出来讲透。源码中大部分复杂查询都用XML方式编写,前端表格的分页搜索功能也主要靠MyBatis的动态SQL支撑。
以传染病报告多条件查询为例,SQL写在ReportCaseMapper.xml中:
<select id="selectReportCasePage" resultType="cn.com.disease.entity.ReportCase"> SELECT rc.id, rc.case_no, rc.patient_name, rc.id_card, rc.disease_code, rc.disease_name, rc.report_type, rc.report_unit, rc.report_doctor, rc.report_date, rc.audit_status FROM tb_report_case rc <where> rc.del_flag = 0 <if test="diseaseCode != null and diseaseCode != ''"> AND rc.disease_code = #{diseaseCode} </if> <if test="auditStatus != null"> AND rc.audit_status = #{auditStatus} </if> <if test="startDate != null"> AND rc.report_date >= #{startDate} </if> <if test="endDate != null"> AND rc.report_date <= #{endDate} </if> <if test="patientName != null and patientName != ''"> AND rc.patient_name LIKE CONCAT('%', #{patientName}, '%') </if> </where> ORDER BY rc.report_date DESC </select>这里有几个MyBatis的关键技巧值得说明。
<where>标签会自动处理第一个AND的问题。如果所有<if>条件都不成立,生成的SQL就是干净的WHERE rc.del_flag = 0,不会出现多余的WHERE AND语法错误。
动态条件里的#{diseaseCode}是预编译参数占位,MyBatis会把它转成?,由PreparedStatement执行,完全规避SQL注入风险。我见过有的项目图省事用${}拼接,在登录接口直接拼用户名,这是极其危险的做法,疾控这类实名数据系统绝对不能用。
关于日期查询,XML里>号直接写会解析报错,必须用>转义。我自己早期写MyBatis经常忘记这个坑,现在凡是XML里出现比较运算,一律改用>或<,或者直接写在Java代码里用@Select注解配合<script>标签避免转义。
LIMIT分页这里没有手写,源码使用的是PageHelper插件。Controller接收pageNum和pageSize参数,Service层调用PageHelper.startPage(pageNum, pageSize),紧接着的第一次查询会自动带上LIMIT,返回结果被封装成PageInfo,里面包含了总记录数、总页数、当前页数据等分页元数据。这种方案的好处在分页查询统计报表时体现得尤其明显,不用自己写COUNT(*)再写SELECT,一个插件全搞定。
3.4 前端核心页面与组件复用
前端这块,源码的亮点在于通用组件抽象做得好。以居民档案选择器为例,在疫苗接种登记、随访记录添加、报告卡创建等多个页面都要用到,源码把它抽成了一个ResidentSelectDialog组件。组件内部封装了分页搜索表格、单选确认逻辑、外部触发方法ref调用,使用方只要一行代码就能弹出选择框。
列表页封装的SearchTable组件也值得学习。它接收搜索字段配置、表格列配置、接口地址三个核心入参,内部整合了ElementUI的el-form、el-table、el-pagination,以及axios请求和loading状态。搜索条件变动后自动重新拉数据,翻页、刷新、重置都在组件内部处理完。实际业务中如果有新模块要开发,只需要写一个几十行的配置对象,就能完整复现一套列表页,效率非常高。
图表展示这块,源码用的是ECharts,封装成了ChartCard组件。首页仪表盘和报表页面复用这个组件,传入不同的option配置即可。疾控业务里最常用的是折线图(发病趋势)、饼图(病种分布、人群类型占比)、柱状图(各区域上报量对比)。ECharts的option配置需要自己手写,源码中提供了几个通用配置模板,可以直接改数据源复用。
3.5 数据联动与复杂查询场景实战
这套系统里最见功力的地方,是跨模块数据联动。拿疫苗接种与居民档案联动举例,从居民档案列表点击“接种记录”,页面会跳转到该居民的接种履历页,接口路径类似/api/vaccine/record/list?residentId=xxx。
后端Service实现里,这个接口做了三件事:先从tb_resident查居民基本信息,再从tb_vaccination_record查该居民所有接种记录,最后从tb_follow_up_record查该居民最近的随访记录。三块数据封装成一个返回对象,前端一个页签展示一块。这种聚合查询不需要写复杂的多表join,而是多次单表查询后内存组装,性能更好,代码也更清晰。
另一个值得玩味的是疫苗效期预警。系统有个定时任务接口,在每日凌晨自动扫描tb_vaccine_batch表中有效期小于90天的批次,生成预警记录写入tb_material_warning表。首页接口查询预警表时,主页会展示即将过期的疫苗批次列表。源码中预警任务用的不是Spring原生@Scheduled注解,而是通过手动触发/api/vaccine/warning/scan接口执行,这样做的目的是让管理员可以随时手动扫描,不必干等凌晨任务,灵活度更高。
4. 环境准备与部署实操
4.1 开发环境搭建完整流程
要跑起这套源码,建议先按下面这套环境准备,版本号是实测兼容的。
JDK必须用1.8或11,选1.8更稳。Maven用3.6以上,Node.js用14.x或16.x,npm 6.x或7.x兼容。MySQL用5.7或8.0均可,如果选8.0,需要注意驱动配置差异,源码里默认driver-class-name是com.mysql.jdbc.Driver,MySQL 8.0可能要改成com.mysql.cj.jdbc.Driver,并且连接URL要追加serverTimezone=Asia/Shanghai,不然会报时区错误。
IDEA打开后端工程后,Maven会自动下载依赖,首次导入会花不少时间。如果遇到下载缓慢,可以配置阿里的镜像仓库。这里给出一段常用的settings.xml镜像配置:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>后端跑起来之前,最关键的一步是先执行数据库脚本。源码的docs/sql目录下通常有一个完整的初始化脚本,包含全部建表语句和初始数据。建议按这个顺序操作:
mysql -uroot -p < disease_control_system.sql执行完成后,检查核心表是否创建成功,再确认sys_user表是否有初始管理员账号。
修改application.yml里的数据库连接配置,核心配置项如下:
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/disease_control?useUnicode=true&characterEncoding=utf8mb4&useSSL=false username: root password: 你的密码 redis: host: localhost port: 6379需要注意,源码中部分模块依赖Redis做缓存,spring-boot-starter-data-redis已经在pom里引入了。如果本地没装Redis,某些依赖缓存的功能会启动失败或运行时异常,建议在Windows或Linux上先装一个Redis,默认端口跑起来即可。
后端启动成功后,控制台会打印Spring Boot的启动日志,显示Tomcat启动端口号。默认配置是8080端口,如果被占用,在application.yml里修改server.port即可。
4.2 前端安装与跨域配置
前端工程目录通常是frontend或ui,用Vue CLI构建,依赖管理用npm。打开工程根目录,执行以下命令,建议两步分开跑:
npm install # 注意:如果某些依赖安装失败,比如 node-sass 版本与本地 node 不兼容导致报错, # 可以尝试用 npm install --force 或直接删除 node_modules 重新安装注意node-sass是前端工程里最容易出问题的依赖,如果本机Node版本偏高(比如18+),node-sass编译大概率会失败。解决办法是换Node 14版本,或者改npm源,再或者把node-sass替换成sass(Dart Sass)。如果只是本地调试,也可以直接用项目里已经锁定的node-sass版本配合Node 14。
安装完依赖后启动开发服务器:
npm run dev启动后Vue CLI默认跑在8080端口,而后端也在8080,端口冲突。前端工程通常在vue.config.js里配置了devServer.port为9528或9527。访问前端地址后,进入登录页,输入初始账号密码即可登录系统。
开发场景下,前端调用后端接口存在跨域问题。源码在vue.config.js里配置了代理:
module.exports = { devServer: { port: 9528, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这样前端请求/api/report/list会自动代理到http://localhost:8080/api/report/list,浏览器不会出现跨域报错。后端application.yml里也配置了CORS,允许跨域访问,双保险。
4.3 生产环境打包发布流程
生产部署比开发环境稍复杂,但掌握了套路也很简单。
前端打包执行npm run build,产物在dist目录。打包后需要检查静态资源引用路径,如果后端接口服务是独立域名,在vue.config.js里配置publicPath为/或相对路径,避免部署到子目录时资源加载不出来。
前端产物有两种部署方式。第一种是把dist目录下的所有文件拷贝到后端工程的src/main/resources/static目录下,重新打包Spring Boot成jar,一键启动即前后端聚合。第二种是前端静态页面部署到Nginx,后端jar独立运行,通过/api反向代理转发请求。我强烈推荐第二种方式,便于独立扩容和发布,部分页面调整不用重启后端服务。
Nginx核心配置参考:
server { listen 8089; server_name your-domain.com; location / { root /opt/dist; index index.html; 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; } }注意try_files配置,Vue Router如果开启history模式,刷新页面时会出现404,必须配置这个回退到index.html。如果不想要这个复杂度,也可以在路由配置里改用hash模式。
后端打包用Maven,命令:
mvn clean package -Dmaven.test.skip=true执行完target目录会产生jar包,体积一般几十MB到上百MB。发布时执行:
java -jar disease-control-system.jar --spring.profiles.active=prod生产库的连接信息配置在application-prod.yml中,建议使用环境变量注入敏感信息,比如密码不要明文写死,用${MYSQL_PASSWORD}形式从环境读取。
5. 常见问题排查与二次开发要点
5.1 启动与运行报错速查表
这套源码我跑过不止一次,也帮朋友远程处理过问题,以下是高频报错及解决办法整理。
| 报错现象 | 根本原因 | 解决办法 |
|---|---|---|
启动时报Table 'disease_control.sys_user' doesn't exist | 数据库脚本未执行或执行不完整 | 重新执行完整SQL脚本,检查是否包含全部建表语句 |
报Access denied for user 'root'@'localhost' | 数据库账号密码与配置不一致 | 修改application.yml中密码,或者用正确的密码重建账号 |
启动报Port 8080 was already in use | 端口被其他进程占用 | 改server.port,或用netstat -ano查占用进程后kill |
| 登录后接口返回401 | token校验失败或过期 | 检查系统时间是否正确,重新登录,确认Redis是否正常 |
前端页面白屏控制台报Failed to load resource: 404 | 静态资源路径错误 | 检查publicPath配置,确认nginx的root路径 |
接口返回Invalid bound statement (not found) | Mapper XML文件没有被扫描到 | 检查mapper-locations配置,确认XML放在正确位置 |
| 中文乱码 | 数据库连接未指定字符集 | 在URL上追加characterEncoding=utf8 |
Timed out after 30000 ms连接池超时 | 数据库连接数上限或网络问题 | 检查连接数配置,确认数据库服务正常 |
5.2 二次开发最常用的扩展点
这套系统的可扩展性相当不错,如果你要基于它做二次开发,有以下几个重点扩展方向。
第一个是增加新的疾病病种。病种字典一般存在tb_disease_dict字典表中,包含病种编码、名称、类别、上报时限等字段。新增病种只需要往这张表插入一条记录,前端下拉选项会自动刷新,因为病种下拉数据是动态从接口拉取的。
第二个是增加新的统计维度。报表模块的核心逻辑在ReportCaseMapper.xml中的统计SQL,如果你想按年龄分段统计发病数,核心思路是基于身份证号计算年龄后分段:
SELECT CASE WHEN TIMESTAMPDIFF(YEAR, STR_TO_DATE(LEFT(id_card, 8), '%Y%m%d'), CURDATE()) < 18 THEN '0-17岁' WHEN TIMESTAMPDIFF(YEAR, STR_TO_DATE(LEFT(id_card, 8), '%Y%m%d'), CURDATE()) < 60 THEN '18-59岁' ELSE '60岁以上' END AS age_group, COUNT(*) AS case_count FROM tb_report_case WHERE del_flag = 0 GROUP BY age_group第三是工作流扩展,如果疾控上报流程需要加入多级审批,可以在tb_report_case表增加current_node字段,再引入工作流引擎,比如Activiti或Flowable。但这套系统本身流程相对简单,如果没有强需求,用状态机字段模拟审批流完全够用。
5.3 性能优化与代码规范建议
数据量上来之后,有几个性能隐患需要提前处理。
报表统计SQL如果超过0.1秒,首先看统计时间范围字段和审核状态字段是否有索引。索引不是越多越好,疾控场景建议保持三个左右高频查询索引,其他低频查询用MySQL慢查询日志定位后按需添加。
分页查询的深分页问题也是重点。当用户翻到第10000页时,LIMIT 100000, 20会扫描前10万行再丢弃,效率极低。优化方案是利用主键或唯一键做延迟关联:
SELECT * FROM tb_report_case rc JOIN ( SELECT id FROM tb_report_case WHERE del_flag = 0 ORDER BY report_date DESC LIMIT 100000, 20 ) t ON rc.id = t.id ORDER BY rc.report_date DESC前端表格可以配合懒加载或滚动加载,减少用户直接跳转超深页码的场景。
代码规范方面,建议保持源码原有的分层结构和命名风格。Controller里不要写业务逻辑,Mapper里只放数据库操作,Service层异常统一转成BusinessException抛出。日志使用slf4j,关键操作(审核、删除、物资出库)务必加上操作人操作时间日志,这是疾控系统审计追踪的基本要求。
6. 写在最后的实操体会
我实际跑通这套系统并小范围试用后,最大的体会是:它比市面上一堆“毕业设计级”的疾控管理系统要完整得多,属于真正理解业务、用心设计过的源码。模块划分贴近疾控中心或大型企业的实际工作流,不是简单堆CRUD。第一次跑通大概花了我三十分钟,大部分时间花在等Maven下载依赖上,真正报错的地方基本没有。
给准备入手这套源码的朋友三个建议。第一,拿到源码先不要急着改,先在本地完整跑一遍,把登录-报告录入-审核-统计这整条链路走通,脑子里有了业务闭环再动手改。第二,重点关注sql目录下的初始化脚本,手动执行一边,注意区分MySQL 5.7和8.0在驱动配置上的差异。第三,二次开发时尽量不动原始表结构,优先通过新增扩展表或者字段冗余的方式做需求,这样后期升级维护成本最低。
我自己的做法是保留了一套干净的原始源码作为基线,在副本上做二次开发,每次需求改动用Git做分支管理。这套系统如果往真实场景引,后面可以做消息推送对接(短信、企业微信)、电子健康档案共享接口、地理信息系统可视化,都有比较好的扩展基础。