展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > 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();
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
精品专题 更多
装机必备

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

Windows

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

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

PDF教程适合刚接触PDF文件的用户,本文整理PDF打开、编辑、转换、合并、压缩等常见基础操作。通过这些教程可以快速了解PDF文件怎么处理,解决办公、学习和资料科整理中的常见问题。使用极轻PDF可在线完成多种PDF操作,适合新手快速上手。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。