毕业设计实战:SSM诊疗预约平台开发全攻略,从挂号到病历一站式搞定!

104 阅读10分钟

毕业设计实战: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&amp;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. 常见开发问题

  1. 号源超卖:使用SELECT FOR UPDATE或乐观锁
  2. 时间冲突:排班时检查时间重叠,预约时检查号源
  3. 数据一致性:关键业务使用@Transactional
  4. 性能瓶颈:热门数据加缓存,复杂查询优化索引

2. 学习资源推荐

  • 视频教程:B站搜索"SSM医疗系统开发实战"
  • 开源项目:GitHub搜索"hospital-management-system"
  • UI设计:Bootstrap官方文档 + 医疗行业UI参考
  • 数据库优化:《MySQL性能调优与架构设计》

祝大家开发顺利,答辩成功!🏥💊