System Architecture Overview
The hospital appointment reservation system employs a three-tier architecture consisting of the presentation layer, business logic layer, and data access layer. The WeChat mini-program serves as the client-side interface, while the backend uses Spring MVC for request handling, MyBatis for database operations, and Vue.js for the management console. This architecture ensures clear separation of concerns and maintainable code structure.
Backend Implementation with Spring Boot
Spring Boot provides auto-configuration capabilities that significantly reduce boilerplate code. The framework integrates embedded servers and offers simplified dependency management through starter packages.
The application entry point and a basic controller implementation:
package com.hospital.appointment;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
@SpringBootApplication
@RestController
@RequestMapping("/api")
public class AppointmentApplication {
public static void main(String[] args) {
SpringApplication.run(AppointmentApplication.class, args);
}
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
return ResponseEntity.ok("System running");
}
@PostMapping("/appointments")
public ResponseEntity<AppointmentResponse> createAppointment(
@RequestBody AppointmentRequest request) {
AppointmentResponse response = appointmentService.bookAppointment(request);
return ResponseEntity.ok(response);
}
}
The controller handles HTTP requests and delegates business logic to the service layer. Spring Boot's auto-configuration eliminates the need for manual dispatcher-servlet and view resolver setup.
Data Access Layer with MyBatis
MyBatis separates SQL statements from application code through XML mapping files or annotations. This approach provides developers with full control over SQL execution while maintaining clean Java interfaces.
Entity class structure:
package com.hospital.appointment.entity;
public class Doctor {
private Long doctorId;
private String doctorName;
private String department;
private String specialty;
private Integer availableSlots;
private String workSchedule;
// getters and setters omitted for brevity
}
The mapper interface defines database operations:
package com.hospital.appointment.mapper;
import com.hospital.appointment.entity.Doctor;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface DoctorMapper {
@Select("SELECT * FROM doctor WHERE department = #{department}")
List<Doctor> findByDepartment(@Param("department") String department);
@Insert("INSERT INTO doctor(name, department, specialty) VALUES(#{name}, #{department}, #{specialty})")
@Options(useGeneratedKeys = true, keyProperty = "doctorId")
int insert(Doctor doctor);
@Update("UPDATE doctor SET available_slots = #{availableSlots} WHERE doctor_id = #{doctorId}")
int updateAvailability(Doctor doctor);
@Delete("DELETE FROM doctor WHERE doctor_id = #{doctorId}")
int deleteById(@Param("doctorId") Long doctorId);
}
Frontend Implementation with Vue.js
Vue.js implements a reactive data binding system that automatically synchronizes the view with underlying data changes. The framework uses a virtual DOM for efficient rendering updates.
Basic Vue component structure:
<template>
<div class="appointment-container">
<h2>Doctor Appointment</h2>
<select v-model="selectedDepartment" @change="loadDoctors">
<option v-for="dept in departments" :key="dept.id" :value="dept.id">
{{ dept.name }}
</option>
</select>
<div class="doctor-list">
<div v-for="doctor in doctors" :key="doctor.id" class="doctor-card">
<h3>{{ doctor.name }}</h3>
<p>Specialty: {{ doctor.specialty }}</p>
<p>Available: {{ doctor.availableSlots }} slots</p>
<button @click="showSchedule(doctor)">View Schedule</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AppointmentBooking',
data() {
return {
selectedDepartment: null,
departments: [],
doctors: []
}
},
methods: {
loadDepartments() {
this.$http.get('/api/departments').then(response => {
this.departments = response.data;
});
},
loadDoctors() {
this.$http.get(`/api/doctors/department/${this.selectedDepartment}`)
.then(response => {
this.doctors = response.data;
});
},
showSchedule(doctor) {
this.$router.push(`/schedule/${doctor.id}`);
}
},
mounted() {
this.loadDepartments();
}
}
</script>
<style scoped>
.appointment-container {
padding: 20px;
}
.doctor-card {
border: 1px solid #ddd;
padding: 15px;
margin: 10px 0;
border-radius: 8px;
}
</style>
Database Schema Design
The appointment system requires tables for departments, doctors, patients, appointments, and time slots.
CREATE TABLE department (
dept_id BIGINT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(100) NOT NULL,
dept_description VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE doctor (
doctor_id BIGINT PRIMARY KEY AUTO_INCREMENT,
doctor_name VARCHAR(50) NOT NULL,
dept_id BIGINT NOT NULL,
specialty VARCHAR(100),
title VARCHAR(50),
phone VARCHAR(20),
available_count INT DEFAULT 0,
FOREIGN KEY (dept_id) REFERENCES department(dept_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE patient (
patient_id BIGINT PRIMARY KEY AUTO_INCREMENT,
patient_name VARCHAR(50) NOT NULL,
id_card VARCHAR(18) NOT NULL UNIQUE,
phone VARCHAR(20),
medical_history TEXT,
register_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE appointment (
appointment_id BIGINT PRIMARY KEY AUTO_INCREMENT,
patient_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
appointment_date DATE NOT NULL,
time_slot VARCHAR(20) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (patient_id) REFERENCES patient(patient_id),
FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX idx_appointment_date ON appointment(appointment_date);
CREATE INDEX idx_doctor_date ON appointment(doctor_id, appointment_date);
Authentication and Authorization
The system implements token-based authentication for secure access. The interceptor validates tokens on each request and maintains user sessions.
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
public static final String AUTH_HEADER = "Authorization";
@Autowired
private TokenManager tokenManager;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (RequestMethod.OPTIONS.name().equals(request.getMethod())) {
response.setStatus(HttpStatus.OK.value());
return false;
}
Method method = ((HandlerMethod) handler).getMethod();
if (method.isAnnotationPresent(PublicAccess.class)) {
return true;
}
String token = request.getHeader(AUTH_HEADER);
if (StringUtils.isEmpty(token)) {
sendUnauthorizedResponse(response, "Missing authentication token");
return false;
}
UserSession session = tokenManager.validateToken(token);
if (session == null) {
sendUnauthorizedResponse(response, "Invalid or expired token");
return false;
}
request.setAttribute("currentUser", session);
return true;
}
private void sendUnauthorizedResponse(HttpServletResponse response, String message)
throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\":\"" + message + "\"}");
}
}
Testing Strategy
System testing validates that all functional modules operate according to specifications. Test cases cover normal workflows, boundary conditions, and error handling scenarios.
Sample login test cases:
| Test Case | Input | Expected | Actual | Status |
|---|---|---|---|---|
| Valid login | username: admin, password: admin123, code: valid | Success, redirect to dashboard | Success, redirect to dashboard | Pass |
| Invalid password | username: admin, password: wrong456, code: valid | Error message displayed | Error message displayed | Pass |
| Empty username | username: "", password: admin123, code: valid | Validation error | Validation error | Pass |
| Expired session | session timeout | Redirect to login | Redirect to login | Pass |
Appointment booking test scenarios:
| Scenario | Input | Expected | Actual | Status |
|---|---|---|---|---|
| Available slot | Select available time slot | Booking confirmation | Booking confirmation | Pass |
| Double booking | Same slot already taken | Error: slot unavailable | Error: slot unavailable | Pass |
| Past date | Select yesterday's date | Error: invalid date | Error: invalid date | Pass |
| Cancel booking | Valid booking ID | Cancellation confirmed | Cancellation confirmed | Pass |
System Configuration
Application configuration in application.yml:
server:
port: 8080
servlet:
context-path: /hospital-system
spring:
datasource:
url: jdbc:mysql://localhost:3306/hospital_db?useSSL=false&serverTimezone=UTC
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.hospital.appointment.entity
logging:
level:
com.hospital.appointment: DEBUG
org.springframework: INFO
API Endpoints
The system provides RESTful endpoints for client integration:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/departments | List all departments |
| GET | /api/doctors/{deptId} | Get doctors by department |
| GET | /api/schedule/{doctorId} | Get doctor's available schedule |
| POST | /api/appointments | Create new appointment |
| GET | /api/appointments/{patientId} | Get patient's appointments |
| PUT | /api/appointments/{id}/cancel | Cancel appointment |
| POST | /api/auth/login | User authentication |
| GET | /api/doctors/{id}/detail | Get doctor information |