
构建一个医患关系管理系统,核心需求包括:
在设计数据模型时,如何有效地表示医生和患者,并处理其特有属性及关系,同时兼顾安全体系的集成,是关键的挑战。
在系统设计初期,通常会考虑以下两种主要的数据模型方案:
这种方案将医生(Doctor)和患者(Patient)分别设计为独立的实体类,并通过 @ManyToMany 注解建立它们之间的关联。
优点: 实体职责明确,各司其职。 缺点:
概念代码示例:
// Doctor.java (简化版)
@Entity
@Getter @Setter
public class Doctor {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
@ManyToMany
@JoinTable(name = "doctor_patient",
joinColumns = @JoinColumn(name = "doctor_id"),
inverseJoinColumns = @JoinColumn(name = "patient_id"))
private Set<Patient> patients = new HashSet<>();
}
// Patient.java (简化版)
@Entity
@Getter @Setter
public class Patient {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
@OneToMany(mappedBy = "patient", cascade = CascadeType.PERSIST)
private List<Medicine> medicineList = new ArrayList<>();
@ManyToMany(mappedBy = "patients")
private Set<Doctor> doctors = new HashSet<>();
}此方案将医生和患者抽象为统一的 User 实体,通过一个 roleType 字段来区分其身份。
优点:
缺点:
概念代码示例:
// User.java (简化版)
@Entity
@Table(name = "MYUSERS")
@Getter @Setter
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private RoleType roleType; // DOCTOR, PATIENT
// 仅针对患者:
@OneToMany(mappedBy = "patient", cascade = CascadeType.PERSIST)
private List<Medicine> medicineList = new ArrayList<>();
// 多对多关系(可能需要自关联或复杂逻辑)
// @ManyToMany with other Users (Doctors/Patients)
}综合以上两种方案的优缺点,推荐采用一种混合模型:将用户共性属性抽取到独立的 User 实体中,而将医生和患者的特有属性及关系分别定义在 Doctor 和 Patient 实体中。Doctor 和 Patient 实体通过 @OneToOne 关系与 User 实体关联。
这种设计既实现了统一的用户认证,又保持了特定角色实体的清晰职责,避免了数据稀疏和业务逻辑的过度耦合。
1. User 实体: 基础用户信息,用于认证。
package com.example.model;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.persistence.*;
@Entity
@Table(name = "users") // 推荐使用更通用的表名
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username; // 用于登录的用户名或邮箱
@Column(nullable = false)
private String password; // 加密后的密码
private String name;
private String surname;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private UserType userType; // DOCTOR, PATIENT
// 可以添加其他通用字段,如电话、地址等
}2. UserType 枚举: 定义用户类型。
package com.example.model;
public enum UserType {
DOCTOR,
PATIENT
}3. Doctor 实体: 医生特有信息及关系。
package com.example.model;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class Doctor {
@Id
private Long id; // 与User的ID共享
@OneToOne
@MapsId // 表示此实体的主键也是其关联User实体的主键
@JoinColumn(name = "id", nullable = false) // 外键列名为id,指向User的id
private User user;
// 医生特有属性,例如:专业领域、执业证书编号等
private String specialty;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) // 医生和患者的多对多关系
@JoinTable(
name = "doctor_patients", // 关联表名
joinColumns = @JoinColumn(name = "doctor_id"), // 本实体在关联表中的外键
inverseJoinColumns = @JoinColumn(name = "patient_id") // 对方实体在关联表中的外键
)
private Set<Patient> patients = new HashSet<>();
public void addPatient(Patient patient) {
this.patients.add(patient);
patient.getDoctors().add(this); // 维护双向关系
}
public void removePatient(Patient patient) {
this.patients.remove(patient);
patient.getDoctors().remove(this); // 维护双向关系
}
}4. Patient 实体: 患者特有信息及关系。
package com.example.model;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class Patient {
@Id
private Long id; // 与User的ID共享
@OneToOne
@MapsId // 表示此实体的主键也是其关联User实体的主键
@JoinColumn(name = "id", nullable = false) // 外键列名为id,指向User的id
private User user;
// 患者特有属性,例如:病史、过敏信息等
private String medicalHistory;
@OneToMany(mappedBy = "patient", cascade = CascadeType.ALL, orphanRemoval = true) // 患者与药品清单的一对多关系
private List<Medicine> medicineList = new ArrayList<>();
@ManyToMany(mappedBy = "patients", fetch = FetchType.LAZY) // 患者与医生的多对多关系,由Doctor维护
private Set<Doctor> doctors = new HashSet<>();
public void addMedicine(Medicine medicine) {
this.medicineList.add(medicine);
medicine.setPatient(this); // 维护双向关系
}
public void removeMedicine(Medicine medicine) {
this.medicineList.remove(medicine);
medicine.setPatient(null); // 维护双向关系
}
}5. Medicine 实体: 药品信息。
package com.example.model;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import javax.persistence.*;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class Medicine {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String dosage; // 剂量
private String frequency; // 频率
@ManyToOne(fetch = FetchType.LAZY) // 药品与患者的多对一关系
@JoinColumn(name = "patient_id", nullable = false)
private Patient patient;
}注意:
在 Spring Boot 中,结合 Spring Security 可以轻松实现基于角色的认证与授权。
UserDetailsService 实现: 创建一个自定义的 UserDetailsService,用于从 User 实体中加载用户信息。
package com.example.security;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
List<GrantedAuthority> authorities = Collections.singletonList(
new SimpleGrantedAuthority("ROLE_" + user.getUserType().name()) // 将UserType映射为Spring Security的角色
);
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
authorities
);
}
}UserRepository 接口:
package com.example.repository;
import com.example.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}Spring Security 配置: 在 WebSecurityConfig 中配置 URL 访问权限。
package com.example.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomUserDetailsService customUserDetailsService;
public WebSecurityConfig(CustomUserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 实际生产环境应启用CSRF防护
.authorizeRequests()
.antMatchers("/register", "/login").permitAll() // 注册和登录接口允许所有人访问
.antMatchers("/api/doctors/**").hasRole("DOCTOR") // 医生相关接口仅限DOCTOR角色访问
.antMatchers("/api/patients/**").hasRole("PATIENT") // 患者相关接口仅限PATIENT角色访问
.antMatchers("/api/medicines/**").hasRole("PATIENT") // 药品相关接口仅限PATIENT角色访问
.anyRequest().authenticated() // 其他所有请求需要认证
.and()
.formLogin() // 或.httpBasic()
.and()
.logout();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}控制器层授权: 在控制器或服务层方法上使用 @PreAuthorize 注解进行更细粒度的控制。
package com.example.controller;
import com.example.model.User;
import com.example.model.UserType;
import com.example.repository.UserRepository;
import com.example.service.DoctorService;
import com.example.service.PatientService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class MyController {
private final UserRepository userRepository;
private final DoctorService doctorService;
private final PatientService patientService;
public MyController(UserRepository userRepository, DoctorService doctorService, PatientService patientService) {
this.userRepository = userRepository;
this.doctorService = doctorService;
this.patientService = patientService;
}
// 获取当前登录用户ID和类型
private User getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();以上就是Spring Boot医患关系管理系统:灵活的数据模型与权限设计的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号