基于SSH框架的医疗预约挂号平台 - 源码深度解析

JavaJavaScriptSSH框架HTMLCSSMySQLJSP+Servlet
2026-03-104 浏览

文章摘要

本系统是基于SSH(Struts2 + Spring + Hibernate)开源框架构建的医疗预约挂号平台,旨在解决传统线下挂号流程繁琐、信息不透明、患者就医体验差等核心痛点。平台通过线上化服务,将医院科室、医生排班、号源状态等信息集中管理并实时更新,使患者能够随时随地查询可预约时段并完成挂号,有...

在医疗信息化快速发展的背景下,传统线下挂号模式暴露出的流程繁琐、信息不对称、资源分配不均等问题日益凸显。一套高效、稳定、易用的线上预约系统成为医疗机构提升服务质量和运营效率的刚需。本系统正是基于这一背景,采用经典的SSH集成框架进行构建,实现了医疗预约挂号的全面线上化。

系统采用典型的三层架构设计,每一层都职责清晰,并通过SSH框架实现了良好的解耦。表现层由Struts2框架负责,它通过核心过滤器StrutsPrepareAndExecuteFilter拦截所有用户请求,并根据struts.xml配置文件中的设定,将请求分发给对应的Action进行处理。Action作为模型的调用者和视图的返回者,是控制层的核心。业务逻辑层由Spring框架的IoC容器统一管理,所有Service层的Bean对象都通过依赖注入的方式被组装起来,从而避免了硬编码带来的紧耦合问题。同时,Spring的声明式事务管理为挂号、取消预约等核心业务操作提供了可靠的数据一致性保障。数据持久层则基于Hibernate实现,它通过对象关系映射将Java对象与数据库表关联,开发者可以完全采用面向对象的方式进行数据库操作,使用HQL语言替代传统的SQL,大大提高了开发效率和代码的可读性。

数据库架构设计与核心表分析

系统的数据模型设计是业务稳定性的基石。整个数据库包含9张核心表,通过精心设计的外键关联,确保了数据的完整性和关联查询的效率。

预约记录表的设计 是系统的核心,它直接关联患者、医生和排班信息,是业务逻辑最复杂的实体之一。

CREATE TABLE `appointment` (
  `appointment_id` int(11) NOT NULL AUTO_INCREMENT,
  `patient_id` int(11) NOT NULL,
  `schedule_id` int(11) NOT NULL,
  `appointment_time` datetime NOT NULL,
  `status` enum('pending','confirmed','completed','cancelled') NOT NULL DEFAULT 'pending',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `notes` text,
  PRIMARY KEY (`appointment_id`),
  KEY `fk_appointment_patient` (`patient_id`),
  KEY `fk_appointment_schedule` (`schedule_id`),
  CONSTRAINT `fk_appointment_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient` (`patient_id`) ON DELETE CASCADE,
  CONSTRAINT `fk_appointment_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `doctor_schedule` (`schedule_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

该表的设计亮点在于:

  1. 状态机设计status字段使用ENUM类型严格定义了预约的生命周期(待确认、已确认、已完成、已取消),任何业务操作都必须遵循此状态流转规则,避免了出现无效或矛盾的预约状态。
  2. 时间戳记录create_time使用CURRENT_TIMESTAMP自动记录创建时间,appointment_time记录具体的就诊时间,为后续的数据分析(如就诊高峰期统计)提供了基础。
  3. 级联删除:外键约束设置了ON DELETE CASCADE,当关联的患者或排班记录被删除时,对应的预约记录会自动清理,有效防止了脏数据的产生。

医生排班表的架构 直接决定了号源管理的精确性和灵活性。

CREATE TABLE `doctor_schedule` (
  `schedule_id` int(11) NOT NULL AUTO_INCREMENT,
  `doctor_id` int(11) NOT NULL,
  `department_id` int(11) NOT NULL,
  `work_date` date NOT NULL,
  `time_slot` enum('morning','afternoon','evening') NOT NULL,
  `total_capacity` int(11) NOT NULL DEFAULT 0,
  `booked_count` int(11) NOT NULL DEFAULT 0,
  `is_available` tinyint(1) NOT NULL DEFAULT 1,
  PRIMARY KEY (`schedule_id`),
  UNIQUE KEY `unique_schedule` (`doctor_id`,`work_date`,`time_slot`),
  KEY `fk_schedule_department` (`department_id`),
  CONSTRAINT `fk_schedule_doctor` FOREIGN KEY (`doctor_id`) REFERENCES `doctor` (`doctor_id`),
  CONSTRAINT `fk_schedule_department` FOREIGN KEY (`department_id`) REFERENCES `department` (`department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

此表设计的精妙之处在于:

  1. 唯一性约束:通过UNIQUE KEY确保了同一医生在同一个日期的同一个时段(上午、下午、晚上)只能有一条排班记录,从根本上杜绝了排班冲突。
  2. 号源动态计算total_capacity(总号源数)和booked_count(已预约数)的分离设计,使得剩余号源可以通过简单的减法实时计算得出,为前端展示和高并发下的号源判断提供了高性能的解决方案。
  3. 排班开关is_available字段作为一个软开关,允许管理员临时关闭某个排班(如医生临时请假),而无需删除记录,保证了数据的可追溯性。

核心功能模块的技术实现

1. 用户认证与权限拦截

用户登录是系统的入口,其安全性和体验至关重要。系统通过Struts2的Action处理登录请求,并利用Spring管理的Service进行业务验证。

LoginAction.java (核心代码片段)

public class LoginAction extends ActionSupport {
    private String username;
    private String password;
    private String userType; // patient, doctor, admin
    private UserService userService; // 由Spring注入

    public String execute() {
        try {
            User user = userService.authenticate(username, password, userType);
            if (user != null) {
                // 将用户信息存入Session
                Map<String, Object> session = ActionContext.getContext().getSession();
                session.put("loggedInUser", user);
                session.put("userRole", userType);
                return SUCCESS;
            } else {
                addActionError("用户名、密码或用户类型错误!");
                return INPUT;
            }
        } catch (Exception e) {
            addActionError("登录过程发生错误:" + e.getMessage());
            return ERROR;
        }
    }
    // Getter and Setter 省略...
}

Struts2 拦截器配置 (struts.xml)

<package name="secure" extends="struts-default" namespace="/">
    <interceptors>
        <interceptor name="authenticationInterceptor" class="com.medical.interceptor.AuthenticationInterceptor"/>
        <interceptor-stack name="secureStack">
            <interceptor-ref name="authenticationInterceptor"/>
            <interceptor-ref name="defaultStack"/>
        </interceptor-stack>
    </interceptors>

    <!-- 默认使用安全拦截栈 -->
    <default-interceptor-ref name="secureStack"/>

    <action name="login" class="loginAction">
        <result name="success" type="redirectAction">home</result>
        <result name="input">/login.jsp</result>
        <result name="error">/login.jsp</result>
    </action>
</package>

用户登录界面

2. 预约挂号业务流程

预约挂号是系统的核心交易链路,涉及复杂的业务规则校验和事务控制。

AppointmentService.java (核心业务逻辑)

@Service
@Transactional // Spring声明式事务注解
public class AppointmentServiceImpl implements AppointmentService {

    @Autowired
    private AppointmentDAO appointmentDAO;
    @Autowired
    private DoctorScheduleDAO scheduleDAO;

    @Override
    public synchronized AppointmentResult makeAppointment(Integer patientId, Integer scheduleId) {
        // 1. 检查排班是否存在且可用
        DoctorSchedule schedule = scheduleDAO.findById(scheduleId);
        if (schedule == null || !schedule.getIsAvailable()) {
            return new AppointmentResult(false, "号源不可用");
        }

        // 2. 检查是否还有剩余号源(高并发场景下需要锁机制)
        if (schedule.getBookedCount() >= schedule.getTotalCapacity()) {
            return new AppointmentResult(false, "号源已满");
        }

        // 3. 检查患者是否已预约同一时段
        boolean hasAppointed = appointmentDAO.checkDuplicateAppointment(patientId, schedule.getWorkDate(), schedule.getTimeSlot());
        if (hasAppointed) {
            return new AppointmentResult(false, "您已预约该时段的号源");
        }

        // 4. 创建预约记录
        Appointment appointment = new Appointment();
        appointment.setPatientId(patientId);
        appointment.setScheduleId(scheduleId);
        appointment.setAppointmentTime(new Date()); // 预约操作时间
        appointment.setStatus(AppointmentStatus.PENDING);

        appointmentDAO.save(appointment);

        // 5. 更新排班表的已预约数量
        schedule.setBookedCount(schedule.getBookedCount() + 1);
        scheduleDAO.update(schedule);

        return new AppointmentResult(true, "预约成功,请等待确认", appointment.getAppointmentId());
    }
}

对应的Hibernate实体映射 Appointment.hbm.xml

<hibernate-mapping>
    <class name="com.medical.model.Appointment" table="appointment">
        <id name="appointmentId" column="appointment_id" type="integer">
            <generator class="identity"/>
        </id>
        <property name="patientId" column="patient_id" type="integer" not-null="true"/>
        <property name="scheduleId" column="schedule_id" type="integer" not-null="true"/>
        <property name="appointmentTime" column="appointment_time" type="timestamp" not-null="true"/>
        <property name="status" column="status" type="string" not-null="true"/>
        <property name="createTime" column="create_time" type="timestamp" insert="false" update="false">
            <column name="create_time" default="CURRENT_TIMESTAMP"/>
        </property>
        <property name="notes" column="notes" type="text"/>
        
        <!-- 多对一关联到医生排班 -->
        <many-to-one name="doctorSchedule" column="schedule_id" class="com.medical.model.DoctorSchedule" insert="false" update="false" not-null="true"/>
    </class>
</hibernate-mapping>

预约记录页面

3. 医生排班与号源管理

后台管理功能允许管理员为医生设置排班,这是整个预约流程的数据基础。

DoctorScheduleAction.java (排班管理)

public class DoctorScheduleAction extends ActionSupport {
    private List<DoctorSchedule> scheduleList;
    private DoctorScheduleService scheduleService;
    private DoctorSchedule currentSchedule;

    // 获取某医生未来一周的排班
    public String listSchedule() {
        Integer doctorId = ...; // 从参数获取
        Date startDate = ...;
        scheduleList = scheduleService.getSchedulesByDoctorAndWeek(doctorId, startDate);
        return SUCCESS;
    }

    // 添加或更新排班
    public String saveOrUpdate() {
        try {
            scheduleService.saveOrUpdateSchedule(currentSchedule);
            addActionMessage("排班信息保存成功!");
            return SUCCESS;
        } catch (Exception e) {
            addActionError("保存失败:" + e.getMessage());
            return ERROR;
        }
    }
}

医生管理界面

4. 基于HQL的复杂查询

系统大量使用Hibernate Query Language进行复杂的数据检索,例如查询某科室下所有医生在指定日期的可用号源。

AppointmentDAOImpl.java (复杂查询示例)

@Repository
public class AppointmentDAOImpl extends HibernateDaoSupport implements AppointmentDAO {

    @Autowired
    public AppointmentDAOImpl(SessionFactory sessionFactory) {
        setSessionFactory(sessionFactory);
    }

    @Override
    public List<DoctorSchedule> findAvailableSchedules(Integer departmentId, Date targetDate) {
        String hql = "FROM DoctorSchedule ds WHERE ds.department.departmentId = :deptId " +
                     "AND ds.workDate = :date " +
                     "AND ds.isAvailable = true " +
                     "AND ds.bookedCount < ds.totalCapacity " +
                     "ORDER BY ds.doctor.doctorName, ds.timeSlot";

        return (List<DoctorSchedule>) getHibernateTemplate().findByNamedParam(hql,
                new String[]{"deptId", "date"},
                new Object[]{departmentId, targetDate});
    }

    @Override
    public boolean checkDuplicateAppointment(Integer patientId, Date workDate, String timeSlot) {
        String hql = "SELECT COUNT(*) FROM Appointment a " +
                     "INNER JOIN a.doctorSchedule ds " +
                     "WHERE a.patientId = :pid " +
                     "AND ds.workDate = :wDate " +
                     "AND ds.timeSlot = :slot " +
                     "AND a.status IN ('pending', 'confirmed')";

        List<?> countList = getHibernateTemplate().findByNamedParam(hql,
                new String[]{"pid", "wDate", "slot"},
                new Object[]{patientId, workDate, timeSlot});

        Long count = (Long) countList.get(0);
        return count > 0;
    }
}

科室专家视图

实体模型与对象关系映射

系统的领域模型通过Hibernate映射清晰地反映了业务实体间的关联。以Patient(患者)、Doctor(医生)和Appointment(预约)为例,它们之间构成了一个典型的网状关系。

Patient.java 实体类 (部分代码)

@Entity
@Table(name = "patient")
public class Patient implements java.io.Serializable {
    private Integer patientId;
    private String patientName;
    private String idCard;
    private String phone;
    private Set<Appointment> appointments = new HashSet<>(0);

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "patient_id", unique = true, nullable = false)
    public Integer getPatientId() { return this.patientId; }
    public void setPatientId(Integer patientId) { this.patientId = patientId; }

    @Column(name = "patient_name", nullable = false, length = 50)
    public String getPatientName() { return this.patientName; }
    public void setPatientName(String patientName) { this.patientName = patientName; }

    // 一对多关系:一个患者可以有多个预约
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "patient")
    public Set<Appointment> getAppointments() { return this.appointments; }
    public void setAppointments(Set<Appointment> appointments) { this.appointments = appointments; }
}

Spring applicationContext.xml 数据源与事务配置

<!-- 数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/medical_db?useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="initialSize" value="5"/>
    <property name="maxTotal" value="20"/>
</bean>

<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
    <property name="mappingResources">
        <list>
            <value>com/medical/model/Patient.hbm.xml</value>
            <value>com/medical/model/Doctor.hbm.xml</value>
            <value>com/medical/model/Appointment.hbm.xml</value>
            <!-- 其他映射文件 -->
        </list>
    </property>
</bean>

<!-- 声明式事务管理 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

系统优化与未来展望

尽管该智慧医疗预约平台已具备核心功能,但在高并发、用户体验和智能化方面仍有持续的优化空间。

  1. 高并发号源处理优化:当前的预约逻辑使用了synchronized关键字,这在单机环境下能防止超卖,但在分布式环境下会失效。未来可引入分布式锁(如基于Redis或ZooKeeper),或采用更高效的数据库悲观锁(SELECT ... FOR UPDATE)来确保在高并发场景下号源计算的绝对准确性。

  2. 服务拆分与微服务化:随着业务增长,单体应用会变得臃肿。可以考虑将系统拆分为独立的微服务,如用户中心服务、预约服务、排班服务、支付服务等。使用Spring Cloud Alibaba等套件进行服务治理,通过API网关聚合服务,提升系统的可维护性、可扩展性和容错能力。

  3. 引入缓存机制提升性能:医生排班、科室信息等读多写少的数据,非常适合引入Redis等缓存中间件。将热点数据缓存起来,可以极大减轻数据库的压力,缩短页面响应时间,提升用户体验。

  4. 智能推荐与数据分析功能:在积累足够多的预约数据后,可以引入大数据分析技术。例如,通过分析历史数据,预测不同科室、不同季节的就诊高峰,为医院资源调配提供决策支持。还可以为患者提供智能推荐,根据症状描述推荐合适的科室和医生。

  5. 增强移动端体验:开发独立的移动App或深度优化微信小程序,利用移动设备的特性(如推送通知),及时向患者发送预约提醒、报告查询等信息,提供比

本文关键词
SSH框架医疗预约挂号平台源码解析数据库设计

上下篇

上一篇
没有更多文章
下一篇
没有更多文章