当前位置:

首页 > 编程开发 > Spring Boot医患系统:灵活数据与权限设计

Spring Boot医患系统:灵活数据与权限设计

本文深入探讨了在SpringBoot中构建医患关系管理系统的核心挑战,包括复杂的用户角色(医生与患者)、多对多关系以及基于角色的安全认证与授权。通过分析常见的两种数据模型方案,文章推荐了一种结合通用用户实体与特定角色实体的混合设计,并详细阐述了其实现细节,包括实体关系映射、代码示例及SpringSecurity集成策略,旨在提供一套灵活且可扩展的解决方案。

本文目录

    Spring Boot医患关系管理系统:灵活的数据模型与权限设计

    本文深入探讨了在Spring Boot中构建医患关系管理系统的核心挑战,包括复杂的用户角色(医生与患者)、多对多关系以及基于角色的安全认证与授权。通过分析常见的两种数据模型方案,文章推荐了一种结合通用用户实体与特定角色实体的混合设计,并详细阐述了其实现细节,包括实体关系映射、代码示例及Spring Security集成策略,旨在提供一套灵活且可扩展的解决方案。

    系统需求与挑战概述

    构建一个医患关系管理系统,核心需求包括:

    1. 用户管理: 区分医生和患者两种用户类型。
    2. 关系管理: 医生可以关联多个患者,患者也可以关联多个医生(多对多关系)。
    3. 特定功能: 患者能够添加和管理自己的用药信息。
    4. 安全认证与授权: 实现用户登录注册,并根据用户角色(医生/患者)控制访问权限。

    在设计数据模型时,如何有效地表示医生和患者,并处理其特有属性及关系,同时兼顾安全体系的集成,是关键的挑战。

    常见数据模型方案探讨

    在系统设计初期,通常会考虑以下两种主要的数据模型方案:

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

    这种方案将医生(Doctor)和患者(Patient)分别设计为独立的实体类,并通过 @ManyToMany 注解建立它们之间的关联。

    优点: 实体职责明确,各司其职。 缺点:

    • 安全管理复杂: 对于登录和注册功能,需要针对 Doctor 和 Patient 分别实现用户认证逻辑,可能导致代码重复或逻辑分散。
    • 公共属性冗余: 医生和患者可能存在姓名、联系方式等公共属性,在两个独立实体中会造成数据冗余。

    概念代码示例:

    // 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 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 medicineList = new ArrayList<>();
    
        @ManyToMany(mappedBy = "patients")
        private Set doctors = new HashSet<>();
    }

    方案二:单一用户表与角色区分

    此方案将医生和患者抽象为统一的 User 实体,通过一个 roleType 字段来区分其身份。

    优点:

    • 统一认证: 简化了用户登录注册流程,所有用户都通过 User 表进行认证。
    • 简化权限管理: 可以基于 roleType 字段直接进行权限控制。

    缺点:

    • 数据稀疏: User 实体中会包含所有角色特有的字段(例如,medicineList 仅适用于患者),导致大量 null 值,造成数据稀疏和模型不清晰。
    • 业务逻辑耦合: 在服务层和控制器层,需要通过 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 medicineList = new ArrayList<>();
    
        // 多对多关系(可能需要自关联或复杂逻辑)
        // @ManyToMany with other Users (Doctors/Patients)
    }

    推荐的数据模型设计:混合方案

    综合以上两种方案的优缺点,推荐采用一种混合模型:将用户共性属性抽取到独立的 User 实体中,而将医生和患者的特有属性及关系分别定义在 Doctor 和 Patient 实体中。Doctor 和 Patient 实体通过 @OneToOne 关系与 User 实体关联。

    核心思想

    • User 实体: 负责存储所有用户的通用信息,如ID、姓名、姓氏、登录凭证(密码、用户名等)以及一个标识用户类型的字段(可选,但推荐用于快速区分)。
    • Doctor 实体: 存储医生特有的信息,并与 User 实体建立一对一关系。它负责管理医生与患者之间的多对多关系。
    • Patient 实体: 存储患者特有的信息,同样与 User 实体建立一对一关系。它负责管理患者的用药信息以及与医生的多对多关系。
    • Medicine 实体: 存储药品信息,并与 Patient 实体建立多对多关系。

    这种设计既实现了统一的用户认证,又保持了特定角色实体的清晰职责,避免了数据稀疏和业务逻辑的过度耦合。

    详细实体类实现

    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 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 medicineList = new ArrayList<>();
    
        @ManyToMany(mappedBy = "patients", fetch = FetchType.LAZY) // 患者与医生的多对多关系,由Doctor维护
        private Set 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;
    }

    注意:

    • @MapsId 注解使得 Doctor 和 Patient 的主键与其关联的 User 实体的主键相同,实现了共享主键的 @OneToOne 关系。
    • @JoinTable 用于定义 Doctor 和 Patient 之间的多对多关系。
    • cascade 类型需要根据业务需求谨慎选择,CascadeType.ALL 表示所有持久化操作(保存、更新、删除等)都会级联到关联实体。

    安全与权限管理

    在 Spring Boot 中,结合 Spring Security 可以轻松实现基于角色的认证与授权。

    1. 用户认证

    • 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 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 {
          Optional findByUsername(String username);
      }

    2. 权限授权

    • 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();
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景

    解析六个GitHub AI开源项目的具体用途,包括软件静态分析、智能体回答检查、多语言配音、求职材料生成、本地代码检索及动态歌词展示,帮助开发者根据实际需求筛选工具并正确验证结果。

    opencode安装skills教程及本地配置方法
    opencode安装skills教程及本地配置方法

    遇到OpenCode Skills无法加载或配置无效?本文手把手教你完成Skills的安装、环境变量设置及本地调试,避开常见坑点,让AI助手更懂你的代码习惯。

    如何开启Visual Studio2022实时代码分析警告提示
    如何开启Visual Studio2022实时代码分析警告提示

    解决 Visual Studio 2022 编辑时不显示代码分析警告的问题。指南涵盖 .NET 项目实时分析设置、Roslyn 分析器范围调整及错误列表来源筛选,确保开发者能高效利用实时代码诊断功能。

    如何在Visual Studio2022查看变量监视窗口调试程序
    如何在Visual Studio2022查看变量监视窗口调试程序

    详解 Visual Studio 2022 调试技巧:包括断点设置、监视窗口开启方法、变量添加步骤以及通过 F10 单步执行观察变量值变化的全过程。

    Visual Studio2017设置默认编码为UTF-8不带BOM
    Visual Studio2017设置默认编码为UTF-8不带BOM

    针对 Visual Studio 2017 缺乏全局默认编码选项的情况,详解如何利用 .editorconfig 设定项目级 UTF-8 无 BOM 规则,以及手动转换现有文件并校验字节的方法。

    Visual Studio2022F5调试提示启动项目未配置怎么修复
    Visual Studio2022F5调试提示启动项目未配置怎么修复

    解决 Visual Studio 2022 F5 调试报错问题,通过设置启动项目、检查解决方案属性及正确区分类库与应用项目,快速恢复调试功能。

    Visual Studio2022NuGet包还原失败离线处理方案
    Visual Studio2022NuGet包还原失败离线处理方案

    解决Visual Studio 2022离线环境中NuGet包还原失败的难题,涵盖依赖链分析、本地源配置、包集收集及隔离验证步骤,帮助开发者构建稳定的离线开发环境。

    Visual Studio2017大项目加载缓慢,优化加载性能技巧
    Visual Studio2017大项目加载缓慢,优化加载性能技巧

    解决Visual Studio 2017大型项目加载慢的实用指南。涵盖性能监测、扩展管理、文档恢复优化及版本兼容性检查,帮助开发者精准定位瓶颈并提升工作效率。

    Visual Studio2022设置多启动项目同时调试运行
    Visual Studio2022设置多启动项目同时调试运行

    详解 Visual Studio 2022 多启动项目配置方法,包括启动动作选择、顺序调整及调试技巧,帮助开发者高效调试分布式或前后端分离应用。

    fastapi下载文件接口实现教程
    fastapi下载文件接口实现教程

    深入解析在FastAPI框架中实现文件下载接口的完整方案,涵盖基于FileResponse的本地静态文件传输、基于StreamingResponse的分块流式传输、RFC 5987中文文件名编码规范、路径穿越安全防御及BackgroundTasks临时文件清理,并配备详细代码示例与架构解析图。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。