简介:本资源是一套完整的基于SpringBoot的老年人体检管理系统毕业设计项目源码,面向计算机专业本科生及Java初学者,解决老年人健康档案管理、体检预约、报告查询等实际业务场景需求。包内共813个文件,涵盖125个Java后端逻辑文件、66个Vue前端页面组件、157个JS交互脚本、162个SVG图标资源及79个GIF动效素材,辅以MySQL建表SQL、配置文件与启动脚本(build.bat/run.bat等),整体压缩包28.71MB,结构清晰,便于分层学习与二次开发。目前已有188人学习下载,资源包含完整项目文档(含摘要、目录、技术选型说明及系统分析章节)、前后端分离代码、数据库设计与多类静态资源,可直接导入IDEA/Eclipse运行,适合作为课程设计、毕设参考或SpringBoot+Vue全栈开发实践范例。
1. 这不是又一个“登录注册+增删改查”的毕设模板,而是一套真实跑在社区卫生服务中心的体检业务闭环系统
很多同学拿到“老年人体检系统”源码第一反应是:又是SpringBoot+Vue的CRUD堆砌?但实际拆开这个项目你会发现,它绕开了学生项目常见的“假数据填充”陷阱,把「体检预约→信息核验→项目执行→报告生成→家属查看」这条链路做实了。比如体检表单里嵌入了血压、血糖、心电图等字段的校验规则(收缩压≥90且≤180),报告导出支持按社区分组统计异常率,家属端能通过身份证号+手机号双重验证查看亲属报告——这些细节说明开发者真去过基层卫生站调研。适合两类人:一是需要交差但不想被答辩老师问“你这系统真能用吗”的应届生;二是想快速搭建区域健康档案管理原型的社区IT运维人员。技术上它不追求高并发或微服务,但把SpringBoot 2.3.x + Vue 2.6 + MyBatis-Plus 3.4.2这套稳定组合用得扎实,MySQL 5.7建模覆盖了体检主表、项目明细表、医生排班表、异常指标预警表四类核心实体。
2. SpringBoot后端:从Maven依赖到MyBatis-Plus动态SQL的落地细节
2.1 Maven依赖配置与JDK1.8兼容性处理
项目使用Maven管理依赖,pom.xml中关键配置需特别注意版本对齐。SpringBoot 2.3.x要求Spring Framework 5.2.x,而MyBatis-Plus 3.4.2在此版本下存在LambdaQueryWrapper泛型推导问题。解决方案是在<properties>中显式声明:
<properties> <java.version>1.8</java.version> <spring-boot.version>2.3.12.RELEASE</spring-boot.version> <mybatis-plus.version>3.4.2</mybatis-plus.version> <mysql-connector-java.version>8.0.26</mysql-connector-java.version> </properties>提示:MySQL驱动必须用8.0+版本,否则连接字符串
?serverTimezone=GMT%2B8&useSSL=false会报时区错误。若本地MySQL为5.7,需在my.cnf中添加default-time-zone='+08:00',否则LocalDateTime字段插入为空。
2.2 MyBatis-Plus多表关联查询的三种实现方式
体检业务中常需联查:体检记录 + 项目明细 + 医生信息 + 异常标记。项目采用混合策略避免N+1问题:
2.2.1 XML方式处理复杂条件查询
ExamRecordMapper.xml中定义体检报告统计查询,使用<resultMap>映射嵌套对象:
<resultMap id="ExamRecordWithDetail" type="com.example.entity.ExamRecord"> <id property="id" column="id"/> <result property="name" column="name"/> <collection property="items" ofType="com.example.entity.ExamItem"> <id property="id" column="item_id"/> <result property="itemName" column="item_name"/> <result property="result" column="item_result"/> <result property="abnormal" column="abnormal"/> </collection> </resultMap> <select id="selectWithItems" resultMap="ExamRecordWithDetail"> SELECT r.id, r.name, i.id as item_id, i.item_name, i.result, i.abnormal FROM exam_record r LEFT JOIN exam_item i ON r.id = i.record_id WHERE r.community_id = #{communityId} AND r.exam_date BETWEEN #{startDate} AND #{endDate} </select>2.2.2 LambdaQueryWrapper构建动态条件
针对管理员筛选场景(如查“空腹血糖>7.0且未转诊”的老人),代码层动态拼接:
LambdaQueryWrapper<ExamRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ExamRecord::getCommunityId, communityId); if (StringUtils.isNotBlank(name)) { wrapper.like(ExamRecord::getName, name); } // 关键:关联子查询判断异常项 wrapper.inSql(ExamRecord::getId, "SELECT record_id FROM exam_item WHERE item_code = 'FPG' AND result > 7.0 AND referral_status = 0" ); List<ExamRecord> records = examRecordService.list(wrapper);2.2.3 Service层手动组装关联数据
对低频但强一致性的操作(如生成PDF报告),采用分步查询减少SQL复杂度:
public ExamReportDTO generateReport(Long recordId) { ExamRecord record = examRecordService.getById(recordId); // 分离查询避免JOIN导致的数据膨胀 List<ExamItem> items = examItemService.list( new LambdaQueryWrapper<ExamItem>().eq(ExamItem::getRecordId, recordId) ); List<Doctor> doctors = doctorService.list( new LambdaQueryWrapper<Doctor>().in(Doctor::getId, record.getDoctorIds()) ); return new ExamReportDTO(record, items, doctors); }2.3 健康指标校验规则的工程化实现
体检数据质量直接影响后续分析,项目将校验逻辑从Controller剥离到独立组件:
@Component public class HealthRuleValidator { // 血压校验:收缩压90-180mmHg,舒张压60-110mmHg public ValidationResult validateBloodPressure(String sys, String dia) { try { int systolic = Integer.parseInt(sys); int diastolic = Integer.parseInt(dia); if (systolic < 90 || systolic > 180 || diastolic < 60 || diastolic > 110) { return new ValidationResult(false, "血压值超出合理范围"); } if (systolic < diastolic) { return new ValidationResult(false, "收缩压不能低于舒张压"); } } catch (NumberFormatException e) { return new ValidationResult(false, "血压值必须为数字"); } return new ValidationResult(true, ""); } }注意:校验结果返回
ValidationResult对象而非布尔值,便于前端展示具体错误文案。所有校验方法均通过@Valid注解在Controller层触发,避免业务逻辑污染。
3. Vue前端:ElementUI组件定制与体检表单的响应式设计
3.1 体检表单的动态渲染机制
体检项目因年龄/性别差异而不同(如女性增加乳腺B超,65岁以上增加认知评估),前端通过v-for动态生成表单项:
<el-form :model="form" :rules="rules" ref="formRef"> <!-- 动态加载项目 --> <div v-for="item in examItems" :key="item.code" class="exam-item"> <el-form-item :label="item.name" :prop="'items.' + item.code + '.result'" :rules="getRule(item)" > <!-- 根据类型渲染不同控件 --> <template v-if="item.type === 'number'"> <el-input-number v-model.number="form.items[item.code].result" :min="item.min" :max="item.max" placeholder="请输入数值" /> </template> <template v-else-if="item.type === 'select'"> <el-select v-model="form.items[item.code].result" placeholder="请选择"> <el-option v-for="opt in item.options" :key="opt.value" :label="opt.label" :value="opt.value" /> </el-select> </template> <span class="unit">{{ item.unit }}</span> </el-form-item> </div> </el-form>examItems数据由后端API/api/exam/items?age=72&gender=female返回,包含每个项目的code、name、type、min/max、unit等元信息。
3.2 ElementUI主题定制与CSS冲突解决
项目引入多个CSS文件(element.min.css、bootstrap.min.css、homeworkPC.min.css),存在按钮样式覆盖问题。解决方案是利用CSS Modules局部作用域:
<style scoped module> /* 仅作用于当前组件 */ .el-button--primary { background-color: #2a5885 !important; border-color: #1d426c !important; } /* 覆盖全局ElementUI的字体大小 */ ::v-deep .el-form-item__label { font-size: 14px; color: #333; } </style>提示:
::v-deep用于穿透scoped样式影响子组件,但需配合<style scoped module>使用。homeworkPC.min.css中定义的.pc-container类被用于布局栅格,需在App.vue根组件中包裹<div class="pc-container">确保样式生效。
3.3 家属端身份核验的双因子验证流程
家属查看报告需通过身份证号+手机号验证,前端实现分步交互:
// step1: 输入身份证号,调用后端校验是否存在该老人 async verifyIdCard() { const res = await this.$http.post('/api/family/verify-idcard', { idCard: this.idCard }); if (res.data.exists) { this.step = 2; // 进入手机号输入页 } else { this.$message.error('未找到该身份证号对应的体检记录'); } }, // step2: 发送短信验证码(调用短信网关API) async sendSms() { await this.$http.post('/api/sms/send', { phone: this.phone, template: 'family_report' }); this.countdown = 60; this.timer = setInterval(() => { this.countdown--; if (this.countdown <= 0) { clearInterval(this.timer); this.countdown = 0; } }, 1000); }, // step3: 提交验证码,获取报告token async submitCode() { const res = await this.$http.post('/api/family/login', { idCard: this.idCard, phone: this.phone, code: this.code }); localStorage.setItem('familyToken', res.data.token); this.$router.push(`/report/${res.data.recordId}`); }4. MySQL数据库设计:从ER图到索引优化的关键决策
4.1 核心表结构与业务语义映射
数据库共12张表,其中4张为主业务表,其设计直指基层体检痛点:
| 表名 | 字段示例 | 业务含义 | 设计要点 |
|---|---|---|---|
exam_record | id,name,id_card,phone,community_id,exam_date,status | 一次体检的主记录 | status枚举值:0-待检、1-已检、2-已报告、3-需复查 |
exam_item | id,record_id,item_code,result,abnormal,referral_status | 具体检查项目结果 | abnormal为TINYINT(1),1表示异常;referral_status标记是否转诊 |
doctor_schedule | id,doctor_id,date,morning_slots,afternoon_slots | 医生排班余量 | morning_slots存储JSON数组["08:00","08:30",...],避免频繁更新 |
community_stats | id,community_id,month,total_count,abnormal_rate,referral_count | 社区月度统计 | 每日定时任务计算,供大屏展示 |
注意:
exam_item.item_code采用编码制(如BP_SYS血压收缩压、FPG空腹血糖),便于后期对接LIS系统,避免中文字段导致的接口兼容问题。
4.2 索引策略与慢查询优化实例
体检报告查询是高频操作,原始SQL存在全表扫描风险:
-- 优化前(无索引):查询某社区某月异常率 SELECT COUNT(*) FROM exam_item WHERE record_id IN ( SELECT id FROM exam_record WHERE community_id = 101 AND exam_date >= '2023-06-01' ) AND abnormal = 1;索引优化方案:
在
exam_record表上建立联合索引:ALTER TABLE exam_record ADD INDEX idx_community_date (community_id, exam_date);在
exam_item表上建立覆盖索引:ALTER TABLE exam_item ADD INDEX idx_record_abnormal (record_id, abnormal);改写SQL为JOIN提升可读性:
SELECT COUNT(*) FROM exam_item i INNER JOIN exam_record r ON i.record_id = r.id WHERE r.community_id = 101 AND r.exam_date >= '2023-06-01' AND i.abnormal = 1;
执行计划显示type从ALL变为ref,rows从12万降至832。
4.3 数据初始化脚本的关键字段处理
1-install.bat执行的初始化SQL包含敏感字段脱敏逻辑:
-- 插入测试数据时,身份证号后4位保留,其余用*替代 INSERT INTO exam_record (name, id_card, phone, community_id, exam_date) VALUES ('张建国', CONCAT(LEFT('110101195001011234', 6), '******', RIGHT('110101195001011234', 4)), '138****1234', 101, '2023-06-15'), ('李秀英', CONCAT(LEFT('110101194802152345', 6), '******', RIGHT('110101194802152345', 4)), '139****5678', 102, '2023-06-16');提示:生产环境部署时需替换为真实脱敏函数,如MySQL 8.0+可用
MASK_EMAIL(),但本项目兼容5.7故采用字符串拼接。
5. 系统部署与本地调试:从bat脚本到Chrome DevTools的完整链路
5.1 三阶段bat脚本的执行逻辑解析
项目提供1-install.bat、2-run.bat、3-build.bat,其执行顺序和依赖关系如下:
| 脚本 | 执行内容 | 关键参数 | 失败排查点 |
|---|---|---|---|
1-install.bat | 初始化数据库(创建库、建表、导入基础数据) | mysql -u root -p123456 < init.sql | 检查MySQL服务是否启动,init.sql路径是否含中文 |
2-run.bat | 启动SpringBoot应用(java -jar target/elderly-exam.jar) | --spring.profiles.active=dev | 若报Port 8080 in use,修改application-dev.yml中server.port |
3-build.bat | Maven打包(mvn clean package -Dmaven.test.skip=true) | -Dmaven.test.skip=true跳过单元测试 | 若提示JAVA_HOME not set,需在系统环境变量中配置JDK1.8路径 |
注意:
2-run.bat中start /b java -jar命令使用/b参数后台运行,关闭CMD窗口不会终止Java进程。如需停止,需在任务管理器中结束java.exe进程。
5.2 Chrome DevTools调试体检表单提交
当家属端提交验证码失败时,可通过DevTools定位问题:
- Network面板:筛选
XHR请求,点击/api/family/login,查看Request Payload是否含idCard、phone、code字段; - Console面板:若出现
Uncaught (in promise) Error: Request failed with status code 400,说明后端校验失败,检查响应体中的message字段; - Application面板:查看
Local Storage中familyToken是否写入,若为空则前端未收到token或存储失败。
常见错误示例及修复:
- 错误:
{"code":400,"message":"验证码错误"}→ 检查Redis中验证码key是否过期(默认5分钟),或application-dev.yml中redis.timeout配置; - 错误:
{"code":500,"message":"No message available"}→ 查看后端日志logs/spring.log,定位NullPointerException发生位置。
5.3 报告PDF生成的字体缺失问题解决
系统使用iText7生成PDF报告,中文显示为方块,根源在于未指定中文字体:
// 修改前:默认字体不支持中文 PdfFont font = PdfFontFactory.createFont(); // 修改后:指定NotoSansCJKsc-Regular.ttf字体文件 String fontPath = "src/main/resources/static/fonts/NotoSansCJKsc-Regular.ttf"; PdfFont font = PdfFontFactory.createFont(fontPath, PdfEncodings.IDENTITY_H);需将字体文件放入resources/static/fonts/目录,并在application.yml中配置:
pdf: font-path: classpath:static/fonts/NotoSansCJKsc-Regular.ttf提示:Noto Sans CJK SC字体可免费商用,下载地址为Google Fonts官网。若生产环境无法外网下载,需提前将字体文件打包进jar包。
本文还有配套的精品资源,点击获取