0

0

Spring Boot 医疗系统实体关系与安全实现指南

聖光之護

聖光之護

发布时间:2025-08-15 15:06:02

|

803人浏览过

|

来源于php中文网

原创

Spring Boot 医疗系统实体关系与安全实现指南

本文旨在探讨在Spring Boot应用中构建医生-患者关系管理系统的最佳实践。我们将深入分析如何设计灵活且可扩展的实体模型,以有效处理医生、患者和药物之间的多对多关联。同时,文章还将详细阐述如何结合Spring Security框架,为不同用户角色(如医生和患者)实现安全认证与精细化权限控制,确保系统的健壮性和数据安全。

1. 核心实体关系设计挑战

在设计医生-患者关系管理系统时,一个核心挑战在于如何有效地建模医生(doctor)、患者(patient)和药物(medicine)之间的复杂关联,并在此基础上实现用户认证与权限管理。常见的两种初步思路是:

  • 方案A:独立实体与多对多关联 将医生和患者分别设计为独立的实体,并通过 @ManyToMany 关联起来。这种方式在数据模型上直观,但面临如何为医生和患者分别实现登录注册及权限控制的问题,可能需要为不同用户类型维护独立的认证流程或端点。

  • 方案B:单一用户表与角色管理 引入一个通用的 User 表,包含角色(如 RoleType 枚举),医生和患者都作为 User 的一种类型。这种方式简化了认证逻辑,但可能导致 User 表中存在大量空字段(如医生没有药物列表,患者没有医生特有的属性),且业务逻辑需要根据用户角色进行大量条件判断,导致代码冗余和复杂性增加。

上述两种方案各有优缺点,但都存在一定局限性。方案A在安全管理上不够统一,而方案B则在数据模型和业务逻辑上引入了不必要的耦合和冗余。因此,我们需要一种更优化的混合方案。

2. 推荐的实体设计方案

为了兼顾通用性、扩展性和业务隔离,推荐采用一种混合实体设计模式:定义一个通用的 User 实体来处理所有用户的基本信息和认证凭据,然后为医生和患者创建各自的专属实体,并通过一对一关系与 User 实体关联。

2.1 实体模型定义

User 实体: 作为所有用户的基础,包含通用属性如ID、姓名、姓氏、以及用户类型(用于权限判断)。

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

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

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private UserType userType; // 用户类型:DOCTOR, PATIENT, ADMIN等
}

// 用户类型枚举
public enum UserType {
    DOCTOR,
    PATIENT,
    ADMIN
}

Doctor 实体: 包含医生特有的属性,并通过 @OneToOne 关联到 User 实体。@MapsId 注解确保 Doctor 的主键与关联的 User 的主键相同,实现共享主键的一对一映射。

import jakarta.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
    @MapsId // 表示此实体的主键是其关联实体的主键
    @JoinColumn(name = "id", nullable = false) // 外键列名
    private User user;

    // 医生与患者之间的多对多关系
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) // 级联操作
    @JoinTable(
        name = "doctor_patients", // 关系表名
        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); // 维护双向关系
    }
}

Patient 实体: 包含患者特有的属性,同样通过 @OneToOne 关联到 User 实体。

import jakarta.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
    @MapsId
    @JoinColumn(name = "id", nullable = false)
    private User user;

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

    // 患者与医生之间的多对多关系(通过Doctor实体维护)
    @ManyToMany(mappedBy = "patients", fetch = FetchType.LAZY) // mappedBy指向Doctor实体中维护关系的字段
    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); // 维护双向关系
    }
}

Medicine 实体: 药物信息。

import jakarta.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<>();
}

这种设计的好处是:

  • 职责分离: User 负责认证和基本身份,Doctor 和 Patient 负责各自特有的业务属性和关系。
  • 避免空字段: Doctor 和 Patient 表只包含各自特有的数据,避免了单一 User 表中的大量空字段。
  • 灵活扩展: 如果未来有新的用户类型(如管理员、护士),只需创建新的实体并关联到 User 即可。
  • 统一认证: 所有用户通过 User 实体进行认证,简化了安全配置。

3. 安全与权限集成 (Spring Security)

Spring Security 是 Spring 生态系统中强大的安全框架,非常适合处理用户认证和授权。

UP简历
UP简历

基于AI技术的免费在线简历制作工具

下载

3.1 用户认证流程

  1. UserDetailsService 实现: Spring Security 通过 UserDetailsService 接口加载用户信息。我们需要实现这个接口,根据用户名从 User 实体中获取用户信息,并将其转换为 Spring Security 的 UserDetails 对象。

    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.stereotype.Service;
    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));
    
            // 将UserType转换为Spring Security的GrantedAuthority
            return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(), // 密码需要是加密的
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getUserType().name()))
            );
        }
    }

    在 User 实体中添加 username 和 password 字段,并确保密码在存储前进行加密(例如使用 BCryptPasswordEncoder)。

  2. Spring Security 配置: 配置 SecurityFilterChain(Spring Boot 2.7+ 推荐)或 WebSecurityConfigurerAdapter(旧版本),定义认证方式、授权规则和密码编码器。

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.SecurityFilterChain;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .csrf(csrf -> csrf.disable()) // 禁用CSRF,方便测试,生产环境请谨慎
                .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/api/public/**").permitAll() // 允许公共访问的API
                    .requestMatchers("/api/doctors/**").hasRole("DOCTOR") // 只有DOCTOR角色才能访问
                    .requestMatchers("/api/patients/**").hasRole("PATIENT") // 只有PATIENT角色才能访问
                    .anyRequest().authenticated() // 其他所有请求都需要认证
                )
                .formLogin(form -> form
                    .permitAll() // 允许所有人访问登录页
                )
                .logout(logout -> logout
                    .permitAll()
                );
            return http.build();
        }
    }

3.2 权限控制与业务逻辑

在控制器或服务层,可以通过 @PreAuthorize 注解或在代码中获取当前认证用户的信息,然后根据 UserType 或其关联的 Doctor/Patient 实体来执行特定的业务逻辑。

  • 基于角色授权:

    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/api/doctors")
    public class DoctorController {
    
        @PreAuthorize("hasRole('DOCTOR')") // 只有DOCTOR角色才能访问
        @GetMapping("/{doctorId}/patients")
        public List getDoctorPatients(@PathVariable Long doctorId) {
            // 获取当前登录的医生,并返回其关联的患者列表
            // 注意:这里需要确保当前登录的医生ID与请求的doctorId匹配,防止越权
            return doctorService.findPatientsByDoctorId(doctorId);
        }
    }
    
    @RestController
    @RequestMapping("/api/patients")
    public class PatientController {
    
        @PreAuthorize("hasRole('PATIENT')") // 只有PATIENT角色才能访问
        @PostMapping("/{patientId}/medicines")
        public void addMedicineToPatient(@PathVariable Long patientId, @RequestBody Medicine medicine) {
            // 只有当前登录的患者才能添加药物
            patientService.addMedicine(patientId, medicine);
        }
    }
  • 获取当前用户信息: 可以通过 SecurityContextHolder 或 @AuthenticationPrincipal 获取当前登录用户的 UserDetails 对象,进而获取其 User 实体,再根据 UserType 进一步加载 Doctor 或 Patient 实体。

    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    
    // 在服务层或控制器中
    public User getCurrentAuthenticatedUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            // 根据userDetails.getUsername()从UserRepository中获取完整的User实体
            return userRepository.findByUsername(userDetails.getUsername()).orElse(null);
        }
        return null;
    }
    
    // 然后可以根据UserType判断并加载具体的Doctor或Patient实体
    public Doctor getCurrentDoctor() {
        User currentUser = getCurrentAuthenticatedUser();
        if (currentUser != null && currentUser.getUserType() == UserType.DOCTOR) {
            return doctorRepository.findById(currentUser.getId()).orElse(null);
        }
        return null;
    }

3.3 特殊情况处理:医生同时是患者

在某些情况下,一个医生也可能同时是患者(例如,医生自己生病需要看病)。这种混合实体设计能够很好地支持这种情况。一个 User 实体可以同时关联到一个 Doctor 实体和一个 Patient 实体。在权限判断时,系统会根据其 UserType(或更精确地说,根据其拥有的角色列表)来决定其可以访问哪些资源和执行哪些操作。

例如,如果一个 User 同时拥有 ROLE_DOCTOR 和 ROLE_PATIENT 两个权限,那么他既可以访问医生相关的API,也可以访问患者相关的API。业务逻辑层则根据具体操作上下文,通过 user.getId() 去查询对应的 Doctor 或 Patient 实体来执行操作。

4. 注意事项与最佳实践

  • 密码加密: 务必使用 PasswordEncoder 对用户密码进行加密存储,切勿明文存储。
  • 事务管理: 确保涉及多个实体操作的业务逻辑处于事务中,以保证数据一致性。
  • 懒加载(Lazy Loading): 对于多对多关系,默认使用 FetchType.LAZY,避免加载不必要的数据,提高性能。只有在真正需要关联数据时才去加载。
  • 级联操作(CascadeType): 谨慎使用 CascadeType.ALL。通常,对于一对一或一对多关系,PERSIST 和 MERGE 足够。在多对多关系中,通常只在关系维护方使用 PERSIST 和 MERGE,避免不必要的级联删除。
  • 服务层设计: 可以设计 UserService 处理通用用户操作,DoctorService 处理医生特有业务,PatientService 处理患者特有业务。这些服务可以互相协作,但保持各自职责清晰。
  • API设计: 考虑为不同角色设计不同的API路径前缀(如 /api/doctors 和 /api/patients),使权限控制更加直观。
  • 异常处理: 实现统一的异常处理机制,为API调用提供友好的错误响应。

总结

通过采用通用 User 实体与特定角色实体(Doctor、Patient)相结合的设计模式,我们能够构建出既能统一管理用户认证,又能灵活处理复杂业务逻辑和实体关系的Spring Boot应用。结合Spring Security,可以实现强大且精细的权限控制,确保医疗系统的数据安全和操作合规性。这种分层、模块化的设计思路,不仅提高了代码的可维护性和可扩展性,也为未来业务需求的变化预留了空间。

相关专题

更多
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

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1018

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

62

2025.10.17

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

0

2026.01.15

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 3.4万人学习

10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

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

共13课时 | 0.9万人学习

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

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