1. 项目概述
作为一名长期从事高校信息化建设的开发者,我最近完成了一个基于SpringBoot的大学生健康体检信息管理系统。这个系统旨在解决传统高校体检管理中存在的诸多痛点:纸质记录易丢失、预约流程繁琐、数据查询不便等问题。
系统采用前后端分离架构,后端使用SpringBoot+MyBatis+MySQL技术栈,前端采用Vue.js+ElementUI,实现了学生体检预约、报告查询、健康咨询等核心功能。经过三个月的开发和测试,系统已在某高校试运行,显著提升了体检管理效率。
提示:在高校场景下,体检系统需要特别考虑学期初的高并发预约情况,我们在系统设计中采用了Redis缓存和消息队列来应对峰值流量。
2. 系统架构设计
2.1 技术选型解析
后端技术栈:
- SpringBoot 2.7.0:简化配置,快速构建
- MyBatis-Plus 3.5.1:增强的ORM框架
- MySQL 8.0:关系型数据库
- Redis 6.2:缓存和会话管理
- RabbitMQ 3.9:异步消息处理
前端技术栈:
- Vue.js 3.2:前端框架
- Element Plus:UI组件库
- Axios:HTTP客户端
- ECharts:数据可视化
选择这些技术的主要考虑:
- SpringBoot的自动配置和起步依赖能大幅减少配置工作量
- Vue.js的响应式特性非常适合体检数据展示
- MySQL在高校IT环境中部署和维护成本低
- Redis能有效缓解学期初的预约高峰压力
2.2 系统架构图
code复制[客户端层]
↓
[API网关层] → [认证中心]
↓
[业务服务层]
├─ 用户服务
├─ 预约服务
├─ 报告服务
└─ 消息服务
↓
[数据存储层]
├─ MySQL
└─ Redis
这种分层架构的优势在于:
- 各服务职责单一,便于维护
- 可针对不同服务独立扩展
- 前后端完全解耦,便于迭代
3. 核心功能实现
3.1 体检预约模块
预约流程设计:
- 学生登录系统,查看可预约项目
- 选择项目和时间段
- 系统校验时间冲突和名额
- 生成预约记录
- 发送预约成功通知
关键代码片段:
java复制@Transactional
public AppointmentResult makeAppointment(AppointmentRequest request) {
// 校验名额
int remaining = projectMapper.getRemainingQuota(request.getProjectId());
if (remaining <= 0) {
throw new BusinessException("该项目已约满");
}
// 校验时间冲突
List<Appointment> exists = appointmentMapper.getStudentAppointments(
request.getStudentId(), request.getAppointDate());
if (!exists.isEmpty()) {
throw new BusinessException("该时间段已有预约");
}
// 创建预约记录
Appointment appointment = new Appointment();
BeanUtils.copyProperties(request, appointment);
appointment.setStatus(AppointmentStatus.BOOKED);
appointmentMapper.insert(appointment);
// 更新项目名额
projectMapper.decreaseQuota(request.getProjectId());
// 发送通知
messageService.sendAppointmentSuccess(appointment);
return new AppointmentResult(appointment);
}
3.2 体检报告模块
报告上传流程:
- 医生登录后台系统
- 选择学生和体检项目
- 上传PDF格式报告文件
- 填写体检结果摘要
- 系统生成报告记录并通知学生
报告查询接口设计:
java复制@GetMapping("/reports/{studentId}")
public PageResult<ReportVO> getStudentReports(
@PathVariable Long studentId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
Page<Report> pageInfo = new Page<>(page, size);
LambdaQueryWrapper<Report> query = Wrappers.lambdaQuery();
query.eq(Report::getStudentId, studentId)
.orderByDesc(Report::getExamDate);
Page<Report> result = reportMapper.selectPage(pageInfo, query);
return PageResult.success(result.convert(this::convertToVO));
}
4. 数据库设计
4.1 核心表结构
学生表(student_info):
sql复制CREATE TABLE `student_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`student_no` varchar(20) NOT NULL COMMENT '学号',
`name` varchar(50) NOT NULL,
`gender` tinyint DEFAULT '0' COMMENT '0-未知 1-男 2-女',
`college` varchar(100) DEFAULT NULL COMMENT '学院',
`major` varchar(100) DEFAULT NULL COMMENT '专业',
`class_name` varchar(50) DEFAULT NULL COMMENT '班级',
`phone` varchar(20) DEFAULT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_no` (`student_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
体检项目表(physical_item):
sql复制CREATE TABLE `physical_item` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`category` varchar(50) NOT NULL COMMENT '项目分类',
`description` text,
`quota` int NOT NULL COMMENT '总名额',
`remaining` int NOT NULL COMMENT '剩余名额',
`start_date` date NOT NULL COMMENT '开始日期',
`end_date` date NOT NULL COMMENT '结束日期',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1-可预约 0-已关闭',
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 索引优化
针对高频查询场景,我们添加了以下索引:
- 预约表的(student_id, project_id)联合索引
- 报告表的(student_id, exam_date)联合索引
- 项目表的(category, status)联合索引
这些索引使核心查询性能提升了3-5倍。
5. 系统部署
5.1 环境要求
- JDK 11+
- MySQL 8.0+
- Redis 6.0+
- Node.js 14+
- Nginx 1.18+
5.2 部署步骤
后端部署:
- 打包应用:
mvn clean package - 上传jar包到服务器
- 创建启动脚本:
bash复制#!/bin/bash
nohup java -jar health-system.jar \
--spring.profiles.active=prod \
--server.port=8080 \
> health.log 2>&1 &
前端部署:
- 构建生产包:
npm run build - 配置Nginx:
nginx复制server {
listen 80;
server_name health.example.com;
location / {
root /opt/health-web/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
}
}
6. 踩坑经验
6.1 并发预约问题
初期版本在高并发预约时出现了超卖问题。解决方案:
- 使用数据库乐观锁控制名额更新
- 引入Redis分布式锁
- 添加排队机制
优化后的预约逻辑:
java复制public boolean reserveProject(Long projectId) {
String lockKey = "project:reserve:" + projectId;
try {
// 获取分布式锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后再试");
}
// 查询并校验名额
PhysicalItem item = itemMapper.selectById(projectId);
if (item.getRemaining() <= 0) {
return false;
}
// 乐观锁更新
int updated = itemMapper.updateRemaining(
projectId, item.getRemaining(), item.getRemaining() - 1);
return updated > 0;
} finally {
redisTemplate.delete(lockKey);
}
}
6.2 文件上传安全性
体检报告上传需要注意:
- 限制文件类型为PDF
- 扫描文件内容是否包含恶意代码
- 存储路径不使用原始文件名
- 设置适当的文件大小限制
安全上传实现:
java复制public String uploadReport(MultipartFile file) {
// 校验文件类型
String contentType = file.getContentType();
if (!"application/pdf".equals(contentType)) {
throw new BusinessException("只支持PDF格式报告");
}
// 校验文件大小
if (file.getSize() > 10 * 1024 * 1024) {
throw new BusinessException("文件大小不能超过10MB");
}
// 生成安全文件名
String originalName = file.getOriginalFilename();
String ext = originalName.substring(originalName.lastIndexOf("."));
String newName = UUID.randomUUID() + ext;
// 存储文件
Path path = Paths.get(uploadDir, newName);
try {
Files.copy(file.getInputStream(), path);
} catch (IOException e) {
throw new BusinessException("文件上传失败");
}
return newName;
}
7. 系统扩展方向
- 移动端适配:开发微信小程序,方便学生随时查询
- 数据分析:基于体检数据生成健康趋势报告
- 智能预约:根据历史数据预测高峰期,智能分配资源
- 健康档案:整合日常健康监测数据,形成完整档案
这个项目让我深刻体会到,高校信息系统开发不仅要考虑技术实现,更要理解教育场景的特殊需求。比如学期初的集中体检、学生作息时间对系统使用的影响等,这些都是在企业系统中较少考虑的因素。