在医疗信息化快速发展的背景下,传统线下挂号模式暴露出的流程繁琐、信息不对称、资源分配不均等问题日益凸显。一套高效、稳定、易用的线上预约系统成为医疗机构提升服务质量和运营效率的刚需。本系统正是基于这一背景,采用经典的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;
该表的设计亮点在于:
- 状态机设计:
status字段使用ENUM类型严格定义了预约的生命周期(待确认、已确认、已完成、已取消),任何业务操作都必须遵循此状态流转规则,避免了出现无效或矛盾的预约状态。 - 时间戳记录:
create_time使用CURRENT_TIMESTAMP自动记录创建时间,appointment_time记录具体的就诊时间,为后续的数据分析(如就诊高峰期统计)提供了基础。 - 级联删除:外键约束设置了
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;
此表设计的精妙之处在于:
- 唯一性约束:通过
UNIQUE KEY确保了同一医生在同一个日期的同一个时段(上午、下午、晚上)只能有一条排班记录,从根本上杜绝了排班冲突。 - 号源动态计算:
total_capacity(总号源数)和booked_count(已预约数)的分离设计,使得剩余号源可以通过简单的减法实时计算得出,为前端展示和高并发下的号源判断提供了高性能的解决方案。 - 排班开关:
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&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"/>
系统优化与未来展望
尽管该智慧医疗预约平台已具备核心功能,但在高并发、用户体验和智能化方面仍有持续的优化空间。
高并发号源处理优化:当前的预约逻辑使用了
synchronized关键字,这在单机环境下能防止超卖,但在分布式环境下会失效。未来可引入分布式锁(如基于Redis或ZooKeeper),或采用更高效的数据库悲观锁(SELECT ... FOR UPDATE)来确保在高并发场景下号源计算的绝对准确性。服务拆分与微服务化:随着业务增长,单体应用会变得臃肿。可以考虑将系统拆分为独立的微服务,如用户中心服务、预约服务、排班服务、支付服务等。使用Spring Cloud Alibaba等套件进行服务治理,通过API网关聚合服务,提升系统的可维护性、可扩展性和容错能力。
引入缓存机制提升性能:医生排班、科室信息等读多写少的数据,非常适合引入Redis等缓存中间件。将热点数据缓存起来,可以极大减轻数据库的压力,缩短页面响应时间,提升用户体验。
智能推荐与数据分析功能:在积累足够多的预约数据后,可以引入大数据分析技术。例如,通过分析历史数据,预测不同科室、不同季节的就诊高峰,为医院资源调配提供决策支持。还可以为患者提供智能推荐,根据症状描述推荐合适的科室和医生。
增强移动端体验:开发独立的移动App或深度优化微信小程序,利用移动设备的特性(如推送通知),及时向患者发送预约提醒、报告查询等信息,提供比