0

0

Spring Boot中医生-患者关系与权限管理实践

碧海醫心

碧海醫心

发布时间:2025-08-15 14:54:19

|

295人浏览过

|

来源于php中文网

原创

Spring Boot中医生-患者关系与权限管理实践

本文探讨在Spring Boot应用中管理医生与患者关系及其权限控制的有效策略。针对多角色用户和复杂业务关系,文章详细比较了多种数据模型,并推荐了一种结合通用用户认证与特定角色数据分离的混合方案。通过清晰的实体设计、JPA注解应用及安全考量,旨在提供一个结构清晰、易于扩展且符合实际业务需求的解决方案。

业务场景与挑战

在构建医疗管理系统时,核心挑战在于如何高效、灵活地管理医生与患者之间的复杂关系,并确保不同角色用户的权限隔离。具体需求包括:

  • 医生与患者的关系管理: 医生可以关联多名患者,患者也可以被多名医生管理,形成多对多(@ManyToMany)关系。
  • 患者的药物信息管理: 患者可以添加并管理其服用的药物信息,药物与患者之间通常是多对多关系(一个药物可能被多个患者服用,一个患者服用多种药物)。
  • 用户认证与权限控制: 系统需要支持用户登录(无论是医生还是患者),并根据其角色(医生或患者)赋予不同的操作权限,例如患者只能管理自己的药物信息,医生可以查看其关联患者的详细信息。

在设计数据模型时,常见的困惑在于:是为每个角色创建独立的实体并处理各自的认证流程,还是采用统一的用户表并结合角色字段来区分?这两种方案各有优缺点,尤其是在安全认证和数据结构灵活性方面。

传统方案评估

在实际开发中,通常会考虑两种基础的数据模型设计思路。

方案一:独立实体与多对多关系

这种方案为每个业务角色(如Doctor和Patient)创建独立的JPA实体。

  • 优点: 实体结构清晰,每个实体只包含其特有的属性,避免了不必要的空字段。业务逻辑可以根据实体类型自然地进行划分。
  • 缺点:
    • 安全认证复杂性: 如果医生和患者都有独立的登录入口和认证逻辑,会导致认证模块的重复开发和管理复杂性。例如,可能需要为Doctor和Patient分别实现UserDetailsService。
    • 用户共享挑战: 如果一个用户可能同时拥有医生和患者的双重身份,此方案难以优雅地处理。
    • 关系管理: Doctor和Patient之间的@ManyToMany关系需要单独维护。

方案二:单一用户表与角色字段

此方案引入一个通用的User实体,其中包含一个roleType字段(如枚举类型DOCTOR, PATIENT)来区分用户类型。

  • 优点:
    • 简化安全认证: 所有用户都通过同一个User实体进行认证,极大简化了Spring Security的集成。
    • 统一用户管理: 用户管理(注册、登录、密码重置)逻辑集中化。
    • 双重身份可能性: 理论上可以通过为同一User赋予多个角色来支持双重身份(但实际业务中可能需要更复杂的逻辑)。
  • 缺点:
    • 数据冗余与空字段: User实体中可能包含所有角色特有的字段。例如,如果Medicine信息只与Patient相关,那么Doctor类型的User在medicineList字段上将是空的,导致数据模型不干净。
    • 业务逻辑耦合: 在服务层或控制器层,需要频繁地根据roleType进行条件判断,将不同角色的业务逻辑混杂在一个类中,增加代码复杂度和维护难度。
    • 实体关系复杂: 如果Doctor和Patient之间需要建立@ManyToMany关系,并且Medicine只关联Patient,那么在单一User实体中管理这些复杂关系会变得非常混乱。

推荐的数据模型与设计

综合考虑上述两种方案的优缺点,推荐一种混合模式:通用用户认证与特定角色数据分离。这种方案既能利用统一用户表简化认证,又能通过角色特有实体保持数据模型的清晰和业务逻辑的解耦。

核心思想

  1. User 实体: 负责存储所有用户的通用信息(如ID、姓名、姓氏、登录凭证、角色类型等),主要用于身份认证和基本信息管理。
  2. Doctor 和 Patient 实体: 存储各自角色特有的属性和关系。它们通过 @OneToOne 关系与 User 实体关联,并使用 @MapsId 注解共享主键,确保一个 User 记录只能对应一个 Doctor 或 Patient 记录(或两者之一)。

实体设计示例

// 1. User 实体:通用用户认证信息
import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "app_users") // 避免与数据库保留字冲突
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username; // 用于登录的用户名
    private String password; // 加密后的密码
    private String name;
    private String surname;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private UserType userType; // DOCTOR 或 PATIENT

    // 可以添加其他通用字段,例如 email, phone 等
}

// UserType 枚举
public enum UserType {
    DOCTOR,
    PATIENT
}

// 2. Doctor 实体:医生特有信息和关系
import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;

@Entity
@Getter
@Setter
public class Doctor {

    @Id
    private Long id; // 与 User 实体共享主键

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId // 表示此实体的主键是其关联实体的主键
    @JoinColumn(name = "id", nullable = false)
    private User user; // 关联的 User 实体

    // 医生特有属性,例如专业领域、执业证书编号等
    private String specialization;

    // 医生与患者的多对多关系
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) // 级联操作
    @JoinTable(
        name = "doctor_patient", // 关系表名
        joinColumns = @JoinColumn(name = "doctor_id"), // 医生表在外键中的列名
        inverseJoinColumns = @JoinColumn(name = "patient_id") // 患者表在外键中的列名
    )
    private Set 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);
    }
}

// 3. Patient 实体:患者特有信息和关系
import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
@Getter
@Setter
public class Patient {

    @Id
    private Long id; // 与 User 实体共享主键

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId // 表示此实体的主键是其关联实体的主键
    @JoinColumn(name = "id", nullable = false)
    private User user; // 关联的 User 实体

    // 患者特有属性,例如病史、过敏信息等
    private String medicalHistory;

    // 患者与药物的多对多关系
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "patient_medicine",
        joinColumns = @JoinColumn(name = "patient_id"),
        inverseJoinColumns = @JoinColumn(name = "medicine_id")
    )
    private Set medicines = new HashSet<>();

    // 患者与医生的多对多关系(通过 Doctor 实体映射)
    @ManyToMany(mappedBy = "patients", fetch = FetchType.LAZY)
    private Set doctors = new HashSet<>();

    public void addMedicine(Medicine medicine) {
        this.medicines.add(medicine);
        medicine.getPatients().add(this);
    }

    public void removeMedicine(Medicine medicine) {
        this.medicines.remove(medicine);
        medicine.getPatients().remove(this);
    }
}

// 4. Medicine 实体:药物信息
import javax.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;

@Entity
@Getter
@Setter
public class Medicine {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;

    // 药物与患者的多对多关系(通过 Patient 实体映射)
    @ManyToMany(mappedBy = "medicines", fetch = FetchType.LAZY)
    private Set patients = new HashSet<>();
}

JPA 注解详解

  • @OneToOne 和 @MapsId:
    • @OneToOne 表示 Doctor 或 Patient 实体与 User 实体之间存在一对一关系。
    • @MapsId 是一个非常关键的注解,它指示JPA使用关联实体(这里是User)的主键作为当前实体(Doctor或Patient)的主键。这意味着Doctor.id和Patient.id的值将直接取自它们关联的User.id。这有效地将Doctor或Patient的记录与一个特定的User记录紧密绑定,并确保了主键的一致性。
    • @JoinColumn(name = "id", nullable = false) 指定了外键列的名称,这里外键列名也叫id,并且不允许为空,强制关联。
  • @ManyToMany 和 @JoinTable:
    • @ManyToMany 用于表示多对多关系,例如医生与患者、患者与药物。
    • @JoinTable 用于定义关系表的名称以及关联双方在关系表中的外键列名。
      • name: 指定中间表的名称。
      • joinColumns: 定义当前实体(拥有@JoinTable的实体)在中间表中的外键列。
      • inverseJoinColumns: 定义关联实体在中间表中的外键列。
    • mappedBy: 在多对多关系中,一方(通常是关系维护方)使用@JoinTable,另一方使用mappedBy来指定关系由哪一方维护,避免重复定义关系表。

安全与权限管理

采用上述混合模型后,Spring Security的实现将变得更加简洁和灵活。

  1. 基于 User 实体进行认证:

    • Spring Security的 UserDetailsService 只需要从 User 仓库中加载用户。
    • 在 User 实体中可以包含 username 和 password 字段用于认证。
    • UserType 字段可以作为用户的角色信息,在 UserDetails 实现中将其转换为 GrantedAuthority。
    // 示例:自定义 UserDetailsService
    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 org.springframework.security.core.authority.SimpleGrantedAuthority;
    import java.util.Collections;
    
    @Service
    public class CustomUserDetailsService implements UserDetailsService {
    
        private final UserRepository 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));
    
            return new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPassword(),
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getUserType().name())) // 将 UserType 映射为角色
            );
        }
    }
  2. 根据用户类型进行权限控制:

    牛小影
    牛小影

    牛小影 - 专业的AI视频画质增强器

    下载
    • 用户登录后,其 UserType 信息已作为角色附加到 SecurityContext 中。
    • 在控制器或服务层,可以通过 @PreAuthorize 注解或手动检查当前用户的角色来限制访问。
    • 例如,一个控制器方法只允许医生访问:@PreAuthorize("hasRole('ROLE_DOCTOR')")。
    • 当需要访问医生或患者特有的数据时,可以先通过 SecurityContextHolder 获取当前登录用户的ID,然后根据其 UserType 从 Doctor 或 Patient 仓库中加载对应的实体。
    // 示例:获取当前登录用户并加载其角色实体
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    
    @Service
    public class UserProfileService {
    
        private final UserRepository userRepository;
        private final DoctorRepository doctorRepository;
        private final PatientRepository patientRepository;
    
        // 构造器注入...
    
        public Object getCurrentUserProfile() {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null || !(authentication.getPrincipal() instanceof UserDetails)) {
                throw new IllegalStateException("User not authenticated.");
            }
    
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            User user = userRepository.findByUsername(userDetails.getUsername())
                                      .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    
            if (user.getUserType() == UserType.DOCTOR) {
                return doctorRepository.findById(user.getId())
                                       .orElseThrow(() -> new RuntimeException("Doctor profile not found"));
            } else if (user.getUserType() == UserType.PATIENT) {
                return patientRepository.findById(user.getId())
                                        .orElseThrow(() -> new RuntimeException("Patient profile not found"));
            } else {
                // 处理其他用户类型或抛出异常
                return null;
            }
        }
    }
  3. 处理“医生同时也是患者”的场景:

    • 当前模型在设计上支持一个User可以同时拥有Doctor和Patient的身份,因为Doctor和Patient实体的主键都映射自User。
    • 若业务允许,可以在注册时为同一User创建Doctor和Patient记录。
    • 在权限控制时,可以根据业务上下文,检查用户是否同时拥有ROLE_DOCTOR和ROLE_PATIENT,然后决定允许的操作。例如,当用户以医生身份操作时,加载Doctor实体;当以患者身份操作时,加载Patient实体。这为更复杂的业务场景提供了极大的灵活性。

服务层设计考量

基于上述实体结构,服务层可以清晰地进行职责划分:

  • UserService: 负责用户的注册、登录、密码修改等通用用户管理操作,主要与 User 实体交互。
  • DoctorService: 负责医生的业务逻辑,如管理医生信息、查看关联患者、分配患者等。它将与 Doctor 实体及其关联的 Patient 实体交互。
  • PatientService: 负责患者的业务逻辑,如管理患者信息、添加/查看药物、查看关联医生等。它将与 Patient 实体及其关联的 Medicine 和 Doctor 实体交互。

这种划分使得每个服务类职责单一,代码可读性高,易于维护和扩展。

总结与展望

本文推荐的 Spring Boot 医生-患者关系数据模型,通过引入一个通用的 User 实体进行统一认证,并利用 @OneToOne 和 @MapsId 将 Doctor 和 Patient 等特定角色实体与 User 实体关联,有效地解决了传统方案中的痛点。

这种混合模型的主要优势在于:

  • 清晰的职责分离: 通用用户数据与特定角色数据各司其职,避免了数据冗余和空字段。
  • 简化的安全认证: Spring Security 只需关注 User 实体,极大降低了认证模块的复杂性。
  • 灵活的权限控制: 能够基于 UserType 进行细粒度的权限控制,并为处理多重身份提供了可能性。
  • 高可扩展性 当引入新的角色(如管理员、药剂师)时,只需创建新的角色实体并关联到 User,而无需修改现有核心逻辑。

在实际应用中,还需要考虑事务管理、错误处理、API设计等多个方面,但一个健壮、清晰的数据模型是构建高效、可维护系统的基石。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

102

2025.08.06

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

135

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

389

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

68

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

32

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

114

2025.12.24

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

534

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

63

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号