百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

Spring Boot实现简单的用户权限管理(超详细版)

xsobi 2024-12-19 17:12 1 浏览

一、前言

为了避免浪费时间进行不必要的阅读,这里先对项目进行简单的介绍。在实际应用场景中,每个用户都有对应的角色,而每个角色又有对应的一些角色。因为一个用户可以有多个角色,一个角色也可以被多个用户所拥有,角色和权限的关系也同理,这里主要利用 多对多的映射关系 将他们联系起来,对他们进行管理。

主要实现的功能:

  1. 添加用户、角色和权限
  2. 删除用户、角色和权限
  3. 给用户添加角色、给角色添加权限
  4. 根据用户名称查询用户拥有的权限

二、项目环境

Java版本:jdk1.8.0_181

IDE:IntelliJ IDEA 2019.1.3

数据库:postgresql 9.5 测试工具:postman

ps:数据库类型不同不要紧,在创建的时候勾选不一样的数据库驱动的就行。

三、项目文件结构

项目创建的时候,需要勾选 Web中的Spring Web StarterSQL中Spring Data JPA、PostgreSQL Driver (如果使用的是mysql数据库,则勾选MySQL Driver),IDEA会自动帮我们在Maven的配置文件中添加相关的依赖。

以下是本项目的目录结构:

四、项目代码

数据库连接配置

  • application.yml
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    username: postgres
    password: 123456
    url: jdbc:postgresql://localhost:5432/postgres
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false
复制代码

如果遇到数据库连接不成功的问题,可以尝试将 properties: 以及之后的部分删除。

1.Entity层

Entity层为数据库实体层,一般一个实体类对应数据库中的一张数据表,类中的属性与数据表中的字段一 一对应。默认情况下,类名即为数据表的表名,属性名则是对应字段名,字段类型也与变量的类型相对应。

本层注解简单解释:

  1. @Entity

该注解用于表明这个类是一个实体类,会给它生成一张对应的数据表。

  1. @Table(name = "table_name")

该注解主要用于修改表名,name的值就是修改的数据表的名称。

  1. @Id

该注解用于声明主键,标在哪个属性上面对应的哪个字段就是主键

  1. @GeneratedValue(strategy = GenerationType.IDENTITY)

该注解的strategy属性主要用于设置主键的增长方式,IDENTITY表示主键由数据库自己生成,从1开始单调递增。

  1. @Column(name = "column_name")

该注解的name属性用于更改数据表的列名,如果不想用默认的就用这个属性改吧

  1. @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)

这个注解得上是本项目的核心了,它声明了实体之间的多对多关系,使两张数据表关联关联起来, 一般是通过生成一张映射表来实现这种映射关系 。关于上面的cascade属性和fetch属性,有兴趣的读者可以查资料了解。

  1. @JoinTable

这个注解是配套@ManyToMany使用的,一般在多对多关系的维护端标注,用于生成上面提到的映射表。一般该注解常用三个属性:name属性表示生成的数据表的名称, joinColumns属性表示关系维护端的主键,inverseJoinColumns则表示关系被维护端的主键 。关于嵌套在里面的@JoinColumn注解,在这里主要用于配置映射表的外键,一般有两个属性:name用于配置外键在映射表中的名称,referencedColumnName 用于表明外键在原表中的字段名称。

  1. @JsonBackReference

关于这个注解,建议先去掉试试然后再加上,对比一下效果。 它主要可以使标注属性避免被json序列化 ,进而避免多对多关系的查询中出现死循环的情况。但是加上了这注解后,就不能进行反向查询了(也就是说不能利用权限名查询拥有这个权限的角色了)

注意:以下代码都省略了要导入的包,getter和setter方法。需要导入相关包可以用快捷键Alt+Insert,用快捷键Alt+Insert然后选择Getter and Setter可以快速生成相关方法。

  • User.java
@Entity
@Table(name = "user_tabel")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer userId;
    @Column(name = "user_name")
    private String userName;
    //关键点
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(
            name = "user_role", //name是表名
            //joinColumns设置的是entity中属性到关系表的映射名称,name是映射表中的字段名
            joinColumns = {@JoinColumn(name = "user_id")},
            //inverseJoinColumns,name是关系实体Role的id在关系表中的名称
            inverseJoinColumns = {@JoinColumn(name = "role_id")}
    )
    private List<Role> roles;
    
    //省略了getter和setter方法
}
复制代码
  • Role.java
@Entity
@Table(name = "role_table")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer roleId;
    @Column(name = "role_name")
    private String roleName;
    //作为被维护端,只需要设置mappedBy属性,其值与User中对应List类型变量名相同
    //@JsonBackReference可以避免属性被json序列化,出现死循环
    @JsonBackReference
    @ManyToMany(mappedBy = "roles")
    private List<User> users;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(
            name = "role_auth", //name是表名
            joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns =@JoinColumn(name = "auth_id")
    )
    private List<Authority> authorities;

	 //省略了getter和setter方法
}
复制代码
  • Authority.java
@Entity
@Table(name = "auth_table")
public class Authority {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "auth_id")
    private Integer authorityId;
    @Column(name = "auth_name")
    private String authorityName;

    @JsonBackReference
    @ManyToMany(mappedBy = "authorities")
    private List<Role> roles;

    //省略了getter和setter方法
}
复制代码

2.dao层

dao层是数据持久层,也被称为mapper层。主要负责访问数据库,向数据库发送SQL语句,完成基础的增删查改任务。主要通过定义继承JpaRepository类的接口来实现,<>中填写的是实体类的名称和该实体主键的变量类型。

在接口中声明的方法不用我们去实现,只要满足命名规则JpaRepository类会自动帮我们生成相应的sql语句。

  • UserRepository.java
public interface UserRepository extends JpaRepository<User, Integer> {
    public List<User> findAllByUserName(String userName);
    public void deleteByUserName(String userName);
}
复制代码
  • RoleRepository.java
public interface RoleRepository extends JpaRepository<Role, Integer> {
    public List<Role> findAllByRoleName(String roleName);
    public void deleteByRoleName(String roleName);
}
复制代码
  • AuthorityRepository.java
public interface AuthorityRepository extends JpaRepository<Authority, Integer> {
    public List<Authority> findAllByAuthorityName(String authorityName);
    public void deleteByAuthorityName(String authorityName);
}
复制代码

3.service层

service层是业务逻辑层, 主要通过调用dao层的接口,接收dao层返回的数据,完成项目的基本功能设计 。由于本项目的service层是在后面参加的,所以有些应该在本层实现的功能写在了controller层orz。

踩到的坑:

  1. 涉及到两张表以上的更新或者删除操作,为了保证数据库的一致性,需要添加 @Transactional事务注解,否则程序会抛出异常。(关于事务的详情,如果不熟悉的话,强烈建议去弄懂。)
  2. 如果要执行删除操作,需要先把它的List先清空,也就相当于把映射表中的关系清除。否则会抛出org.hibernate.exception.ConstraintViolationException异常。(我这里用到了多种清除方式:如果删除维护端数据,只是把维护端的List清空就行;如果删除被维护端的数据,则把用户(维护端)的List中要移除的角色(被维护端)都remove掉,不知道我是不是想多了)
  • EntityService.java
@Service
public class EntityService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private AuthorityRepository authorityRepository;

    @Transactional
    public void deleteUser(String userName) {
        List<User> users = userRepository.findAllByUserName(userName);
        //如果删除维护端数据,只是把维护端的List清空
        for(User user : users) {
            user.getRoles().clear();
            userRepository.save(user); //执行save()之后才会保存到数据库中
        }
        userRepository.deleteByUserName(userName);
    }

    @Transactional
    public void deleteRole(String roleName) {
        List<Role> roles = roleRepository.findAllByRoleName(roleName);
        List<User> users = userRepository.findAll();
        for (User user : users) {
            List<Role> userRole = user.getRoles();
            for (Role role : roles) {
                if (userRole.contains(role)) {
                    userRole.remove(role);
                }
                role.getAuthorities().clear();
                roleRepository.save(role);
            }
            userRepository.save(user);
        }
        roleRepository.deleteByRoleName(roleName);
    }

    @Transactional
    public void deleteAuthority(String authName) {
        List<Authority> authorities = authorityRepository.findAllByAuthorityName(authName);
        List<Role> roles = roleRepository.findAll();
        //如果删除被维护端的数据,则把用户(维护端)的List中要移除的角色(被维护端)都remove掉
        for (Role role : roles) {
            List<Authority> roleAuthoritis = role.getAuthorities();
            for (Authority authority : authorities) {
                if (roleAuthoritis.contains(authority)) {
                    roleAuthoritis.remove(authority);
                }
            }
            roleRepository.save(role);
        }
        authorityRepository.deleteByAuthorityName(authName);
    }

}
复制代码

4.controller层

controller层是控制层,其功能为请求和响应控制,负责前后端交互,接受前端请求,调用service层,接收service层返回的数据,最后返回具体的页面和数据到客户端。

本层注解简单解释:

  1. @RestController

Spring4之后新加入的注解,相当于@Controller + @ResponseBody。 @Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。当然也有语义化的作用,即代表该类是充当Controller的作用 @ResponseBody 它的作用简单来说说就是指该类中所有的API接口返回的数据,甭管你对应的方法返回Map或是其他Object,它会以Json字符串的形式返回给客户端,根据尝试,如果返回的是String类型,则仍然是String。

  1. @RequestMapping("/user")

该注解用来处理请求地址的映射,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

  1. @Autowired

养成看源代码的好习惯,在IDEA中按住Ctrl键点击该注解,可以查看该注解的解析。我理解了一下,大概就是调用这个类的构造方法对这个类进行实例化操作。

  1. @RequestParam(value = "userName")

该注解可以获取请求报文中的数据(数据一般以键值对方式传输),把然后把获取到的数据复制给方法的参数中。例如上面就是获取名为"userName"的数据值。

再简单介绍一下,增加用户、角色和权限的操作。 一般我们添加的时候,是先添加权限,再添加角色,最后添加角色 (可以联想一下,是不是先有权限才能给角色分配呀)。有些人会对如何关联用户和角色、角色和权限有疑惑(包括一开始的自己), 在实体类中存在一个List对象,只要在其中添加对应的对象映射表中就会创建好映射关系。 (可以看看下面添加的代码,然后自己做实验观察现象)

只要这个不是你的第一个spring boot程序,相信你都看得懂。如果感觉功能不够,读者还可以自行添加。

  • EntityController
@RestController
@RequestMapping("/user")
public class EntityController {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired
    private EntityService entityService;

    /*
        用户部分的增删查改
     */
    @RequestMapping("/finduser")
    public List<User> findByName(@RequestParam(value = "userName") String userName) {
        return userRepository.findAllByUserName(userName);
    }

    @RequestMapping("/findalluser")
    public List<User> findAllUser() {
        return userRepository.findAll();
    }

    @RequestMapping("/adduser")
    public List<User> addUser(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "roleName") String roleName) {
        User user = new User();
        Role role = roleRepository.findAllByRoleName(roleName).get(0);
        user.setUserName(userName);
        user.setRoles(new ArrayList<>());
        user.getRoles().add(role);//给用户设置权限
        userRepository.save(user);
        return userRepository.findAll();
    }

    /*
        给用户添加角色
     */
    @RequestMapping("/adduserrole")
    public List<User> addUserRole(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "roleName") String roleName) {
        User user = userRepository.findAllByUserName(userName).get(0);
        Role role = roleRepository.findAllByRoleName(roleName).get(0);
        if (user.getRoles() == null) {
            user.setRoles(new ArrayList<>());
        }
        user.getRoles().add(role);//给用户设置权限
        userRepository.save(user);
        return userRepository.findAll();
    }

    @RequestMapping("/deleteuser")
    public List<User> deleteUser(
            @RequestParam(value = "userName") String userName) {
        entityService.deleteUser(userName);
        return userRepository.findAll();
    }

    /*
        查询用户权限
     */
    @RequestMapping("/getauth")
    public Set<Authority> getAuthority(
            @RequestParam(value = "userName") String userName) {
        Set<Authority> authoritieSet = new HashSet<>();
        User user = userRepository.findAllByUserName(userName).get(0);
        for(Role role : user.getRoles()){
            for(Authority authority : role.getAuthorities()) {
                authoritieSet.add(authority);
            }
        }
        return authoritieSet;
    }

    /*
        角色部分的增删查改
     */
    @RequestMapping("/findallrole")
    public List<Role> findAllRole() {
        return roleRepository.findAll();
    }

    @RequestMapping("/addrole")
    public List<Role> addRole(
            @RequestParam(value = "roleName") String roleName,
            @RequestParam(value = "authName") String authName) {
        Role role = new Role();
        Authority authority = authorityRepository.findAllByAuthorityName(authName).get(0);
        role.setRoleName(roleName);
        role.setAuthorities(new ArrayList<>());
        role.getAuthorities().add(authority);
        roleRepository.save(role);
        return roleRepository.findAll();
    }

    /*
        给角色添加权限
     */
    @RequestMapping("/addroleauth")
    public List<Role> addRoleAuth(
            @RequestParam(value = "roleName") String roleName,
            @RequestParam(value = "authName") String authName) {
        Role role = roleRepository.findAllByRoleName(roleName).get(0);
        Authority authority = authorityRepository.findAllByAuthorityName(authName).get(0);
        if (role.getAuthorities() == null) {
            role.setAuthorities(new ArrayList<>());
        }
        role.getAuthorities().add(authority);
        roleRepository.save(role);
        return roleRepository.findAll();
    }

    @RequestMapping("/deleterole")
    public List<Role> deleteRole(
            @RequestParam(value = "roleName") String roleName) {
        entityService.deleteRole(roleName);
        return roleRepository.findAll();
    }

    /*
        权限部分的增删查改
     */
    @RequestMapping("/findallauth")
    public List<Authority> findAllAuthority() {
        return authorityRepository.findAll();
    }

    @RequestMapping("/addauth")
    public List<Authority> addAuthority(
            @RequestParam(value = "authName" ) String authName) {
        Authority authority = new Authority();
        authority.setAuthorityName(authName);
        authorityRepository.save(authority);
        return authorityRepository.findAll();
    }

    @RequestMapping("/deleteauth")
    public List<Authority> deletAuthority(
            @RequestParam(value = "authName") String authName) {
        entityService.deleteAuthority(authName);
        return authorityRepository.findAll();
    }
}
复制代码

五、运行效果

写的函数有点多,这里挑选一部分来演示吧。

  • 数据表

在程序运行之后,它会自动为我们在数据库中创建5张表,其中包括3个实体对应的数据表以及2张映射表。

  • 查询操作

由于先前已经进行了一些实验,数据表中已经有了少量的数据,所以我们就现在演示查询吧。 首先按照上文说的添加顺序,先是权限的查询。

接着是角色的查询:

接着是用户查询:

最后,我们通过用户名来查询他拥有的权限。

  • 增加角色操作

添加权限的操作很常规不做演示,添加用户的操作和添加角色的操作差不多可以借鉴。

如果您觉得文章对您有帮助,可以点赞评论转发支持一下~~

原文链接:https://www.tuicool.com/articles/UfiuAff

相关推荐

js向对象中添加元素(对象,数组) js对象里面添加元素

一、添加一个元素对象名["属性名"]=值(值:可以是一个值,可以是一个对象,也可以是一个数组)这样添加进去的元素,就是一个值或对象或数组...

JS小技巧,如何去重对象数组?(一)

大家好,关于数组对象去重的业务场景,想必大家都遇到过类似的需求吧,这对这样的需求你是怎么做的呢。下面我就先和大家分享下如果是基于对象的1个属性是怎么去重实现的。方法一:使用.filter()和....

「C/C++」之数组、vector对象和array对象的比较

数组学习过C语言的,对数组应该都不会陌生,于是这里就不再对数组进行展开介绍。模板类vector模板类vector类似于string,也是一种动态数组。能够在运行阶段设置vector对象的长度,可以在末...

如何用sessionStorage保存对象和数组

背景:在工作中,我将[{},{}]对象数组形式,存储到sessionStorage,然后ta变成了我看不懂的形式,然后我想取之用之,发现不可能了~记录这次深刻的教训。$clickCouponIndex...

JavaScript Array 对象 javascript的array对象

Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...

JavaScript中的数组Array(对象) js array数组

1:数组Array:-数组也是一个对象-数组也是用来存储数据的-和object不同,数组中可以存储一组有序的数据,-数组中存储的数据我们称其为元素(element)-数组中的每一个元素都有一...

数组和对象方法&amp;数组去重 数组去重的5种方法前端

列举一下JavaScript数组和对象有哪些原生方法?数组:arr.concat(arr1,arr2,arrn);--合并两个或多个数组。此方法不会修改原有数组,而是返回一个新数组...

C++ 类如何定义对象数组?初始化数组?linux C++第43讲

对象数组学过C语言的读者对数组的概念应该很熟悉了。数组的元素可以是int类型的变量,例如int...

ElasticSearch第六篇:复合数据类型-数组,对象

在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON...

第58条:区分数组对象和类数组对象

示例设想有两个不同类的API。第一个是位向量:有序的位集合varbits=newBitVector;bits.enable(4);bits.enable([1,3,8,17]);b...

八皇后问题解法(Common Lisp实现)

如何才能在一张国际象棋的棋盘上摆上八个皇后而不致使她们互相威胁呢?这个著名的问题可以方便地通过一种树搜索方法来解决。首先,我们需要写一个函数来判断棋盘上的两个皇后是否互相威协。在国际象棋中,皇后可以沿...

visual lisp修改颜色的模板函数 怎么更改visual studio的配色

(defunBF-yansemokuai(tuyuanyanse/ss)...

用中望CAD加载LISP程序技巧 中望cad2015怎么加载燕秀

1、首先请加载lisp程序,加载方法如下:在菜单栏选择工具——加载应用程序——添加,选择lisp程序然后加载,然后选择添加到启动组。2、然后是添加自定义栏以及图标,方法如下(以...

图的深度优先搜索和广度优先搜索(Common Lisp实现)

为了便于描述,本文中的图指的是下图所示的无向图。搜索指:搜索从S到F的一条路径。若存在,则以表的形式返回路径;若不存在,则返回nil。...

两个有助于理解Common Lisp宏的例子

在Lisp中,函数和数据具有相同的形式。这是Lisp语言的一个重大特色。一个Lisp函数可以分析另一个Lisp函数;甚至可以和另一个Lisp函数组成一个整体,并加以利用。Lisp的宏,是实现上述特色的...