基于最新技术的病人挂号系统实操指南
随着Java生态的不断发展,病人挂号系统的技术实现也在持续演进。本文将采用当前最新的技术栈(Spring Boot 3.x、Spring Security 6.x、Vue 3等),提供一套完整的实操方案,帮助开发者快速构建现代化的医疗挂号系统。
技术栈选择与优势
后端技术栈
- Spring Boot 3.2.x:基于Spring Framework 6.x,支持Java 17+,提供更好的性能和新特性
- Spring Security 6.x:增强的安全机制,支持JWT认证和OAuth2
- Spring Data JPA:简化数据访问层,减少样板代码
- H2/MySQL 8:数据库选择,H2适合开发环境,MySQL用于生产
- Lombok:减少 getter/setter 等模板代码
- MapStruct:类型转换工具,比手动转换更高效且不易出错
- SpringDoc-OpenAPI:自动生成API文档,替代Swagger
前端技术栈
- Vue 3:采用Composition API,性能更优
- Pinia:Vue 3推荐的状态管理库,替代Vuex
- Element Plus:基于Vue 3的UI组件库
- Axios:处理HTTP请求
- Vue Router 4:路由管理
实操步骤
第一步:搭建后端项目
-
使用Spring Initializr创建项目
访问 Spring Initializr,选择以下配置:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.0
- Java: 17
- Dependencies: Spring Web, Spring Security, Spring Data JPA, H2 Database, Lombok
-
配置数据库连接
在
application.yml
中添加配置:
spring:datasource:url: jdbc:h2:mem:hospitaldbdriverClassName: org.h2.Driverusername: sapassword: h2:console:enabled: truejpa:database-platform: org.hibernate.dialect.H2Dialecthibernate:ddl-auto: updateshow-sql: true
-
创建核心实体类
首先创建患者(Patient)实体:
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Patient {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@NotBlank(message = "姓名不能为空")private String name;@Enumerated(EnumType.STRING)private Gender gender;@Past(message = "出生日期必须是过去的日期")private LocalDate birthDate;@NotBlank(message = "联系电话不能为空")@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码格式不正确")private String phone;@NotBlank(message = "身份证号不能为空")@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$", message = "身份证号格式不正确")private String idCard;private LocalDateTime createTime;private LocalDateTime updateTime;
}public enum Gender {MALE, FEMALE, OTHER
}
接着创建医生(Doctor)和挂号(Registration)实体(代码略)
- 创建Repository接口
public interface PatientRepository extends JpaRepository<Patient, Long> {Optional<Patient> findByIdCard(String idCard);Optional<Patient> findByPhone(String phone);
}
- 实现服务层
@Service
@RequiredArgsConstructor
@Transactional
public class PatientServiceImpl implements PatientService {private final PatientRepository patientRepository;@Overridepublic Patient register(PatientDto patientDto) {// 检查身份证号是否已注册if (patientRepository.findByIdCard(patientDto.getIdCard()).isPresent()) {throw new DuplicateResourceException("该身份证号已注册");}// 检查手机号是否已注册if (patientRepository.findByPhone(patientDto.getPhone()).isPresent()) {throw new DuplicateResourceException("该手机号已注册");}// 转换DTO为实体Patient patient = Patient.builder().name(patientDto.getName()).gender(patientDto.getGender()).birthDate(patientDto.getBirthDate()).phone(patientDto.getPhone()).idCard(patientDto.getIdCard()).createTime(LocalDateTime.now()).updateTime(LocalDateTime.now()).build();return patientRepository.save(patient);}// 其他方法实现...
}
- 配置Spring Security
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final JwtAuthenticationFilter jwtAuthFilter;private final AuthenticationProvider authenticationProvider;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> auth.requestMatchers("/api/v1/auth/**", "/h2-console/**").permitAll().requestMatchers("/api/v1/admin/**").hasRole("ADMIN").anyRequest().authenticated()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authenticationProvider(authenticationProvider).addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class).headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()));return http.build();}
}
第二步:实现核心业务功能
-
患者注册与登录
使用JWT实现无状态认证,创建认证控制器:
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthenticationController {private final AuthenticationService service;@PostMapping("/register")public ResponseEntity<AuthenticationResponse> register(@Valid @RequestBody RegisterRequest request) {return ResponseEntity.ok(service.register(request));}@PostMapping("/authenticate")public ResponseEntity<AuthenticationResponse> authenticate(@Valid @RequestBody AuthenticationRequest request) {return ResponseEntity.ok(service.authenticate(request));}
}
- 挂号功能实现
@Service
@RequiredArgsConstructor
public class RegistrationServiceImpl implements RegistrationService {private final RegistrationRepository registrationRepository;private final PatientRepository patientRepository;private final DoctorRepository doctorRepository;private final ScheduleRepository scheduleRepository;@Override@Transactionalpublic RegistrationDto createRegistration(RegistrationRequest request) {// 验证患者是否存在Patient patient = patientRepository.findById(request.getPatientId()).orElseThrow(() -> new ResourceNotFoundException("患者不存在"));// 验证医生是否存在Doctor doctor = doctorRepository.findById(request.getDoctorId()).orElseThrow(() -> new ResourceNotFoundException("医生不存在"));// 验证排班是否存在且有余号Schedule schedule = scheduleRepository.findById(request.getScheduleId()).orElseThrow(() -> new ResourceNotFoundException("排班信息不存在"));if (schedule.getAvailableNumber() <= 0) {throw new BusinessException("该时段号源已用尽");}// 创建挂号记录Registration registration = Registration.builder().patient(patient).doctor(doctor).schedule(schedule).registrationTime(LocalDateTime.now()).status(RegistrationStatus.PENDING).build();// 减少可用号源schedule.setAvailableNumber(schedule.getAvailableNumber() - 1);scheduleRepository.save(schedule);Registration savedRegistration = registrationRepository.save(registration);return mapToDto(savedRegistration);}// 其他方法...
}
第三步:前端实现
- 创建Vue 3项目
npm create vue@latest hospital-registration-frontend
cd hospital-registration-frontend
npm install
npm install element-plus axios pinia vue-router
- 配置路由
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView.vue'
import RegisterView from '../views/RegisterView.vue'
import PatientDashboard from '../views/patient/Dashboard.vue'
import AppointmentView from '../views/patient/AppointmentView.vue'
import AdminDashboard from '../views/admin/Dashboard.vue'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/',name: 'home',component: HomeView},{path: '/login',name: 'login',component: LoginView},{path: '/register',name: 'register',component: RegisterView},{path: '/patient/dashboard',name: 'patientDashboard',component: PatientDashboard,meta: { requiresAuth: true, role: 'PATIENT' }},{path: '/patient/appointment',name: 'appointment',component: AppointmentView,meta: { requiresAuth: true, role: 'PATIENT' }},{path: '/admin/dashboard',name: 'adminDashboard',component: AdminDashboard,meta: { requiresAuth: true, role: 'ADMIN' }}]
})// 路由守卫
router.beforeEach((to, from, next) => {const token = localStorage.getItem('token')const role = localStorage.getItem('role')if (to.meta.requiresAuth && !token) {next('/login')} else if (to.meta.role && to.meta.role !== role) {next('/')} else {next()}
})export default router
- 实现患者注册组件
<template><el-card class="register-card"><h2>患者注册</h2><el-form :model="form" :rules="rules" ref="formRef" label-width="120px"><el-form-item label="姓名" prop="name"><el-input v-model="form.name"></el-input></el-form-item><el-form-item label="性别" prop="gender"><el-radio-group v-model="form.gender"><el-radio label="MALE">男</el-radio><el-radio label="FEMALE">女</el-radio><el-radio label="OTHER">其他</el-radio></el-radio-group></el-form-item><el-form-item label="出生日期" prop="birthDate"><el-date-pickerv-model="form.birthDate"type="date"format="YYYY-MM-DD"placeholder="选择出生日期"></el-date-picker></el-form-item><el-form-item label="手机号码" prop="phone"><el-input v-model="form.phone"></el-input></el-form-item><el-form-item label="身份证号" prop="idCard"><el-input v-model="form.idCard"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="form.password" type="password"></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm">注册</el-button><el-button @click="resetForm">重置</el-button></el-form-item></el-form></el-card>
</template><script setup>
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { register } from '../api/auth';const router = useRouter();
const formRef = ref(null);const form = reactive({name: '',gender: 'MALE',birthDate: '',phone: '',idCard: '',password: ''
});const rules = {name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],gender: [{ required: true, message: '请选择性别', trigger: 'change' }],birthDate: [{ required: true, message: '请选择出生日期', trigger: 'change' }],phone: [{ required: true, message: '请输入手机号码', trigger: 'blur' },{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }],idCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' },{ pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, message: '请输入正确的身份证号', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }]
};const submitForm = async () => {try {await formRef.value.validate();const response = await register(form);ElMessage.success('注册成功,请登录');router.push('/login');} catch (error) {if (error.name === 'ValidationError') {return;}ElMessage.error(error.response?.data?.message || '注册失败');}
};const resetForm = () => {formRef.value.resetFields();
};
</script><style scoped>
.register-card {max-width: 600px;margin: 50px auto;padding: 20px;
}
</style>
- 实现挂号功能组件(略)
第四步:系统测试与部署
- 单元测试
使用JUnit 5和Mockito进行服务层测试:
@ExtendWith(MockitoExtension.class)
public class PatientServiceTest {@Mockprivate PatientRepository patientRepository;@InjectMocksprivate PatientServiceImpl patientService;@Testvoid shouldRegisterPatientSuccessfully() {// ArrangePatientDto patientDto = new PatientDto("张三", Gender.MALE, LocalDate.of(1990, 1, 1),"13800138000", "110101199001011234", "password123");Patient patient = Patient.builder().id(1L).name("张三").gender(Gender.MALE).birthDate(LocalDate.of(1990, 1, 1)).phone("13800138000").idCard("110101199001011234").build();when(patientRepository.findByIdCard(anyString())).thenReturn(Optional.empty());when(patientRepository.findByPhone(anyString())).thenReturn(Optional.empty());when(patientRepository.save(any(Patient.class))).thenReturn(patient);// ActPatient result = patientService.register(patientDto);// AssertassertNotNull(result);assertEquals("张三", result.getName());verify(patientRepository).save(any(Patient.class));}@Testvoid shouldThrowExceptionWhenIdCardExists() {// ArrangePatientDto patientDto = new PatientDto("张三", Gender.MALE, LocalDate.of(1990, 1, 1),"13800138000", "110101199001011234", "password123");when(patientRepository.findByIdCard(anyString())).thenReturn(Optional.of(new Patient()));// Act & AssertassertThrows(DuplicateResourceException.class, () -> {patientService.register(patientDto);});verify(patientRepository, never()).save(any());}
}
- 部署方案
- 后端:使用Docker容器化部署,创建
Dockerfile
:
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
- 前端:构建静态文件后部署到Nginx
npm run build
- 使用Docker Compose编排服务:
version: '3.8'
services:backend:build: ./backendports:- "8080:8080"depends_on:- dbenvironment:- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/hospitaldb- SPRING_DATASOURCE_USERNAME=root- SPRING_DATASOURCE_PASSWORD=passwordfrontend:build: ./frontendports:- "80:80"depends_on:- backenddb:image: mysql:8.0ports:- "3306:3306"environment:- MYSQL_ROOT_PASSWORD=password- MYSQL_DATABASE=hospitaldbvolumes:- mysql-data:/var/lib/mysqlvolumes:mysql-data:
系统扩展与优化建议
-
性能优化
- 实现号源缓存,减少数据库访问
- 对热门医生的挂号请求进行限流
- 使用异步处理邮件通知等非核心流程
-
功能扩展
- 集成在线支付功能(支付宝、微信支付)
- 实现电子病历管理
- 添加短信通知功能
- 开发医生出诊计划管理
-
安全增强
- 实现API接口限流防止滥用
- 添加敏感操作日志记录
- 定期数据备份策略
通过以上步骤,我们基于最新的Java技术栈构建了一个功能完善的病人挂号系统。该系统不仅实现了核心的挂号功能,还考虑了安全性、可扩展性和用户体验,适合中小型医院使用。开发者可以根据实际需求,在此基础上进行二次开发和定制。
最新技术,病人挂号系统,实操指南,分步骤操作要点,技术支撑挂号,医院挂号系统,挂号操作要点,智能挂号步骤,就医挂号指南,挂号系统使用技巧,线上挂号步骤,数字化挂号实操,高效挂号要点,医院挂号步骤,智能就医指南