毕业设计实战:SSM诊疗预约平台开发全攻略,从挂号到病历一站式搞定!
当初做诊疗预约系统时,最头疼的就是“号源管理”和“时间冲突检测”——医生排班冲突、患者重复预约、号源超卖这些坑踩了个遍!还有病历模板、药品库存、缴费状态这些业务逻辑,调试了好几天才理顺。今天就把诊疗预约平台开发全流程拆解清楚,跟着做就能高效完成毕设!
一、先搞清楚“诊疗预约平台到底要做什么”!
1. 核心业务场景(别再跑偏了!)
诊疗预约平台≠医院HIS系统!核心是“在线预约+电子病历+药品管理”:
-
患者端核心功能:
- 医生查询:按科室、职称、擅长搜索医生
- 在线预约:选择时间段、支付挂号费
- 病历查看:个人就诊记录、电子病历
- 药品查询:药品信息、库存状态
- 医生收藏:收藏喜欢的医生,方便复诊
-
医生端核心功能:
- 排班管理:设置可预约时间段
- 患者管理:查看预约列表、患者病历
- 电子病历:开具诊断、药方
- 留言回复:回复患者咨询
- 数据统计:工作量统计、患者评价
-
管理员端核心功能:
- 基础数据:医生管理、科室设置、药品目录
- 号源管理:医生排班、号源分配
- 系统监控:预约统计、药品库存预警
- 财务管理:挂号费统计、缴费管理
2. 核心业务流程(必须理清!)
患者预约:查询医生 → 选择时间 → 支付挂号费 → 生成预约订单
医生接诊:查看排班 → 接诊患者 → 开具病历 → 确认缴费
药品管理:开具药方 → 扣减库存 → 缴费取药
3. 技术选型建议(避坑指南)
- 后端:SSM(Spring + SpringMVC + MyBatis)
- 数据库:MySQL 8.0(必须utf8mb4编码)
- 前端:JSP + Bootstrap 5 + jQuery
- 缓存:Ehcache(可选,缓存医生信息)
- 支付:模拟支付(毕设够用,不用接真实支付)
为什么选SSM而不是SpringBoot?
- 学校教材多用SSM,参考资料更丰富
- 配置文件虽然多,但能更好理解MVC架构
- 答辩时老师更熟悉,提问更容易应对
二、数据库设计:诊疗业务的核心
1. 核心表结构优化设计
医生表(核心业务表):
CREATE TABLE doctor (
id INT PRIMARY KEY AUTO_INCREMENT,
doctor_no VARCHAR(50) UNIQUE COMMENT '医生工号',
doctor_name VARCHAR(50) NOT NULL COMMENT '医生姓名',
department_id INT NOT NULL COMMENT '科室ID',
title_id INT COMMENT '职称ID',
specialty TEXT COMMENT '擅长领域',
introduction TEXT COMMENT '医生介绍',
avatar_url VARCHAR(255) COMMENT '头像路径',
reg_fee DECIMAL(10,2) DEFAULT 0 COMMENT '挂号费',
like_count INT DEFAULT 0 COMMENT '点赞数',
dislike_count INT DEFAULT 0 COMMENT '踩数',
status TINYINT DEFAULT 1 COMMENT '1-正常 2-停诊 3-休假',
work_years INT COMMENT '工作年限',
education VARCHAR(100) COMMENT '学历',
is_recommend TINYINT DEFAULT 0 COMMENT '是否推荐',
INDEX idx_department (department_id),
INDEX idx_status (status),
INDEX idx_recommend (is_recommend)
) COMMENT='医生信息表';
医生排班表(号源管理关键):
CREATE TABLE doctor_schedule (
id INT PRIMARY KEY AUTO_INCREMENT,
doctor_id INT NOT NULL COMMENT '医生ID',
schedule_date DATE NOT NULL COMMENT '排班日期',
time_slot INT NOT NULL COMMENT '时间段 1-上午 2-下午 3-晚上',
max_patients INT DEFAULT 20 COMMENT '最大预约数',
current_patients INT DEFAULT 0 COMMENT '当前预约数',
schedule_status TINYINT DEFAULT 1 COMMENT '1-可预约 2-已满 3-停诊',
start_time TIME COMMENT '开始时间',
end_time TIME COMMENT '结束时间',
remark VARCHAR(200) COMMENT '备注',
UNIQUE KEY uk_doctor_time (doctor_id, schedule_date, time_slot),
INDEX idx_date_status (schedule_date, schedule_status),
INDEX idx_doctor_date (doctor_id, schedule_date)
) COMMENT='医生排班表';
预约订单表(状态流转核心):
CREATE TABLE appointment_order (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50) UNIQUE COMMENT '预约单号',
patient_id INT NOT NULL COMMENT '患者ID',
doctor_id INT NOT NULL COMMENT '医生ID',
schedule_id INT NOT NULL COMMENT '排班ID',
appointment_date DATE NOT NULL COMMENT '预约日期',
time_slot INT NOT NULL COMMENT '时间段',
order_status TINYINT DEFAULT 1 COMMENT '1-待支付 2-已支付 3-已取消 4-已完成 5-已过期',
payment_status TINYINT DEFAULT 1 COMMENT '1-未支付 2-已支付',
reg_fee DECIMAL(10,2) COMMENT '挂号费',
payment_method INT COMMENT '支付方式',
payment_time DATETIME COMMENT '支付时间',
cancel_reason VARCHAR(200) COMMENT '取消原因',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_patient (patient_id),
INDEX idx_doctor (doctor_id),
INDEX idx_status (order_status),
INDEX idx_appointment_date (appointment_date)
) COMMENT='预约订单表';
2. 复杂业务查询示例
查询医生可预约时间段:
-- 查询某医生未来7天可预约的排班
SELECT
ds.schedule_date AS '排班日期',
CASE ds.time_slot
WHEN 1 THEN '上午'
WHEN 2 THEN '下午'
WHEN 3 THEN '晚上'
END AS '时间段',
ds.start_time AS '开始时间',
ds.end_time AS '结束时间',
ds.max_patients AS '总号源',
ds.current_patients AS '已预约',
(ds.max_patients - ds.current_patients) AS '剩余号源',
ds.reg_fee AS '挂号费',
CASE
WHEN ds.current_patients >= ds.max_patients THEN '已满'
WHEN ds.schedule_status = 3 THEN '停诊'
ELSE '可预约'
END AS '状态'
FROM doctor_schedule ds
WHERE ds.doctor_id = #{doctorId}
AND ds.schedule_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)
AND ds.schedule_status = 1 -- 可预约状态
AND ds.current_patients < ds.max_patients -- 还有剩余号源
ORDER BY ds.schedule_date, ds.time_slot;
查询患者预约历史:
-- 查询患者的预约记录及就诊情况
SELECT
ao.order_no AS '预约单号',
d.doctor_name AS '医生',
dept.department_name AS '科室',
ao.appointment_date AS '预约日期',
CASE ao.time_slot
WHEN 1 THEN '上午'
WHEN 2 THEN '下午'
WHEN 3 THEN '晚上'
END AS '时间段',
ao.reg_fee AS '挂号费',
CASE ao.order_status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已取消'
WHEN 4 THEN '已完成'
WHEN 5 THEN '已过期'
END AS '订单状态',
CASE WHEN mr.id IS NOT NULL THEN '已就诊' ELSE '未就诊' END AS '就诊状态',
mr.diagnosis AS '诊断结果',
ao.create_time AS '预约时间'
FROM appointment_order ao
JOIN doctor d ON ao.doctor_id = d.id
JOIN department dept ON d.department_id = dept.id
LEFT JOIN medical_record mr ON ao.id = mr.appointment_id
WHERE ao.patient_id = #{patientId}
ORDER BY ao.appointment_date DESC, ao.create_time DESC;
三、核心功能实现要点
1. 电子病历管理(结构化存储)
病历实体设计:
@Data
@TableName("medical_record")
public class MedicalRecord {
@TableId(type = IdType.AUTO)
private Long id;
private String recordNo; // 病历编号
private Long patientId; // 患者ID
private Long doctorId; // 医生ID
private Long appointmentId; // 预约ID
// 基本信息
private String chiefComplaint; // 主诉
private String presentIllness; // 现病史
private String pastHistory; // 既往史
private String personalHistory; // 个人史
private String familyHistory; // 家族史
// 体格检查
private String temperature; // 体温
private String bloodPressure; // 血压
private String heartRate; // 心率
private String physicalExam; // 体格检查结果
// 诊断信息
private String diagnosis; // 诊断
private String treatmentPlan; // 治疗方案
private String advice; // 医嘱
// 处方信息(JSON格式存储药品列表)
private String prescription;
private BigDecimal totalFee; // 总费用
private Integer paymentStatus; // 缴费状态
private Date visitDate; // 就诊日期
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
处方药品JSON结构:
{
"prescriptionItems": [
{
"medicineId": 1,
"medicineName": "阿莫西林胶囊",
"specification": "0.25g*24粒",
"quantity": 2,
"unit": "盒",
"usage": "口服,一次2粒,一日3次",
"days": 7,
"price": 25.50,
"subtotal": 51.00
},
{
"medicineId": 2,
"medicineName": "布洛芬缓释胶囊",
"specification": "0.3g*20粒",
"quantity": 1,
"unit": "盒",
"usage": "口服,一次1粒,一日2次",
"days": 3,
"price": 18.00,
"subtotal": 18.00
}
],
"totalAmount": 69.00,
"doctorAdvice": "多喝水,注意休息"
}
3. 医生排班管理(防止时间冲突)
排班冲突检测:
@Service
public class ScheduleService {
public Result addSchedule(DoctorSchedule schedule) {
// 检查同一医生同一时间段是否已存在排班
LambdaQueryWrapper<DoctorSchedule> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DoctorSchedule::getDoctorId, schedule.getDoctorId())
.eq(DoctorSchedule::getScheduleDate, schedule.getScheduleDate())
.eq(DoctorSchedule::getTimeSlot, schedule.getTimeSlot())
.ne(schedule.getId() != null, DoctorSchedule::getId, schedule.getId());
if (scheduleMapper.selectCount(wrapper) > 0) {
return Result.error("该时间段已有排班,请勿重复设置");
}
// 检查时间是否合理(比如上午排班不能设置晚上时间)
if (!isValidTimeSlot(schedule.getTimeSlot(), schedule.getStartTime())) {
return Result.error("时间段设置不合理");
}
// 设置默认值
if (schedule.getMaxPatients() == null) {
schedule.setMaxPatients(20); // 默认20个号源
}
schedule.setCurrentPatients(0);
schedule.setScheduleStatus(1); // 可预约
scheduleMapper.insert(schedule);
return Result.success("排班设置成功");
}
private boolean isValidTimeSlot(Integer timeSlot, Time startTime) {
// 时间段验证逻辑
switch (timeSlot) {
case 1: // 上午:8:00-12:00
return startTime.getHours() >= 8 && startTime.getHours() < 12;
case 2: // 下午:14:00-18:00
return startTime.getHours() >= 14 && startTime.getHours() < 18;
case 3: // 晚上:18:00-21:00
return startTime.getHours() >= 18 && startTime.getHours() < 21;
default:
return false;
}
}
}
四、前端页面设计要点
1. 预约时间选择页面
<div class="schedule-container">
<!-- 日期选择 -->
<div class="date-selector">
<div class="date-list">
<c:forEach var="date" items="${dateList}">
<div class="date-item ${date.isToday ? 'active' : ''}"
onclick="selectDate('${date.dateStr}')">
<div class="date-week">${date.week}</div>
<div class="date-day">${date.day}</div>
<div class="date-month">${date.month}月</div>
</div>
</c:forEach>
</div>
</div>
<!-- 时间段选择 -->
<div class="time-slot-container">
<div class="time-slot-header">
<h5>${selectedDate} 可预约时间段</h5>
<div class="legend">
<span class="legend-item available"><i></i> 可预约</span>
<span class="legend-item full"><i></i> 已满</span>
<span class="legend-item unavailable"><i></i> 不可约</span>
</div>
</div>
<div class="time-slot-list">
<c:forEach var="slot" items="${timeSlots}">
<div class="time-slot-card ${slot.statusClass}"
onclick="${slot.clickable ? 'selectTimeSlot(' + slot.scheduleId + ')' : ''}">
<div class="slot-time">
<div class="slot-period">${slot.period}</div>
<div class="slot-range">${slot.startTime}-${slot.endTime}</div>
</div>
<div class="slot-info">
<div class="slot-status">${slot.statusText}</div>
<div class="slot-quota">
号源:${slot.currentPatients}/${slot.maxPatients}
</div>
<div class="slot-fee">¥${slot.regFee}</div>
</div>
<c:if test="${slot.clickable}">
<div class="slot-action">
<button class="btn btn-sm btn-primary">预约</button>
</div>
</c:if>
</div>
</c:forEach>
</div>
</div>
<!-- 预约确认弹窗 -->
<div class="modal fade" id="confirmModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">确认预约</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>医生:${doctor.doctorName}</p>
<p>科室:${doctor.departmentName}</p>
<p>预约时间:<span id="confirmDate"></span> <span id="confirmTime"></span></p>
<p>挂号费:<span id="confirmFee"></span>元</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="submitAppointment()">确认预约</button>
</div>
</div>
</div>
</div>
</div>
<script>
let selectedScheduleId = null;
function selectDate(dateStr) {
window.location.href = '${pageContext.request.contextPath}/appointment/schedule?doctorId=${doctor.id}&date=' + dateStr;
}
function selectTimeSlot(scheduleId) {
selectedScheduleId = scheduleId;
// 获取选中时间段的信息
const slot = $('.time-slot-card[data-schedule-id="' + scheduleId + '"]');
$('#confirmDate').text('${selectedDate}');
$('#confirmTime').text(slot.find('.slot-period').text() + ' ' + slot.find('.slot-range').text());
$('#confirmFee').text(slot.find('.slot-fee').text().replace('¥', ''));
// 显示确认弹窗
new bootstrap.Modal(document.getElementById('confirmModal')).show();
}
function submitAppointment() {
$.post('${pageContext.request.contextPath}/appointment/create', {
scheduleId: selectedScheduleId
}, function(response) {
if (response.success) {
alert('预约成功!');
window.location.href = '${pageContext.request.contextPath}/appointment/my';
} else {
alert(response.message);
}
});
}
</script>
五、系统测试要点
1. 业务流程测试用例
| 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 患者预约 | 选择医生 → 选择时间 → 确认预约 → 支付 | 预约成功,号源减少,生成订单 |
| 取消预约 | 患者取消待支付/已支付订单 | 订单状态变取消,号源释放,退款记录 |
| 医生接诊 | 医生查看预约列表 → 接诊患者 → 开具病历 | 病历生成,药品库存扣减,缴费状态更新 |
| 药品库存 | 开具药方时检查库存 → 库存不足提示 | 库存充足时扣减,不足时提示并建议替代药品 |
2. 并发测试重点
- 多患者同时预约同一医生同一时间段
- 医生修改排班时患者正在预约
- 药品库存并发扣减
- 医生同时接诊多个患者
3. 边界测试
- 医生排班时间冲突
- 患者重复预约
- 号源已满时预约
- 药品库存为0时开具药方
六、部署上线方案
1. Tomcat部署配置
<!-- web.xml配置 -->
<web-app>
<!-- 字符编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!-- SpringMVC前端控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
</web-app>
2. 数据库连接池配置
<!-- applicationContext.xml -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/medical_db?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="60000"/>
</bean>
七、答辩准备核心要点
1. 演示流程设计
1. 患者注册登录 → 查询医生 → 预约挂号
2. 医生登录 → 查看排班 → 接诊患者 → 开具病历
3. 管理员登录 → 医生管理 → 药品管理 → 数据统计
4. 患者查看 → 个人病历 → 预约记录 → 药品信息
2. 技术亮点阐述
- 号源管理机制:乐观锁防止超卖,状态机控制预约流程
- 电子病历系统:结构化存储,支持药品JSON配置
- 排班冲突检测:时间验证,防止医生时间重叠
- 数据安全性:患者隐私保护,操作日志记录
3. 常见问题准备
Q:如何防止号源超卖? A:使用数据库乐观锁,预约时检查当前号源数,确保原子性操作
Q:患者隐私如何保护? A:敏感信息加密存储,访问权限控制,操作日志审计
Q:系统能支持多少并发预约? A:通过数据库连接池优化、Redis缓存热门数据、异步处理,可支持1000+并发
Q:如何扩展系统功能? A:模块化设计,可添加在线咨询、体检预约、远程会诊等功能模块
八、开发时间规划建议
- 第1周:需求分析 + 业务流程梳理 + 技术选型
- 第2周:数据库设计 + SSM框架搭建 + 基础功能开发
- 第3周:核心业务开发(预约/病历/药品)
- 第4周:前端页面开发 + 业务逻辑联调
- 第5周:系统测试 + Bug修复 + 性能优化
- 第6周:部署配置 + 文档编写 + 答辩准备
九、避坑指南与学习资源
1. 常见开发问题
- 号源超卖:使用SELECT FOR UPDATE或乐观锁
- 时间冲突:排班时检查时间重叠,预约时检查号源
- 数据一致性:关键业务使用@Transactional
- 性能瓶颈:热门数据加缓存,复杂查询优化索引
2. 学习资源推荐
- 视频教程:B站搜索"SSM医疗系统开发实战"
- 开源项目:GitHub搜索"hospital-management-system"
- UI设计:Bootstrap官方文档 + 医疗行业UI参考
- 数据库优化:《MySQL性能调优与架构设计》
祝大家开发顺利,答辩成功!🏥💊