Spring Boot实现简单的用户权限管理(超详细版)
文章目录
一、前言
为了避免浪费时间进行不必要的阅读,这里先对项目进行简单的介绍。在实际应用场景中,每个用户都有对应的角色,而每个角色又有对应的一些角色。因为一个用户可以有多个角色,一个角色也可以被多个用户所拥有,角色和权限的关系也同理,这里主要利用多对多的映射关系将他们联系起来,对他们进行管理。
主要实现的功能:
- 添加用户、角色和权限
- 删除用户、角色和权限
- 给用户添加角色、给角色添加权限
- 根据用户名称查询用户拥有的权限
二、项目环境
Java版本:jdk1.8.0_181
IDE:IntelliJ IDEA 2019.1.3
数据库:postgresql 9.5
测试工具:postman
ps:数据库类型不同不要紧,在创建的时候勾选不一样的数据库驱动的就行。
三、项目文件结构
项目创建的时候,需要勾选Web中的Spring Web Starter和SQL中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
1.Entity层
Entity层为数据库实体层,一般一个实体类对应数据库中的一张数据表,类中的属性与数据表中的字段一 一对应。默认情况下,类名即为数据表的表名,属性名则是对应字段名,字段类型也与变量的类型相对应。
本层注解简单解释:
- @Entity
该注解用于表明这个类是一个实体类,会给他生成一张对应的数据表。 - @Table(name = “table_name”)
该注解主要用于修改表名,name的值就是修改的数据表的名称。 - @Id
该注解用于声明主键,标在哪个属性上面对应的哪个字段就是主键 - @GeneratedValue(strategy = GenerationType.IDENTITY)
该注解的strategy属性主要用于设置主键的增长方式,IDENTITY表示主键由数据库自己生成,从1开始单调递增。 - @Column(name = “column_name”)
该注解的name属性用于更改数据表的列名,如果不想用默认的就用这个属性改吧 - @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
这个注解得上是本项目得核心了,它声明了实体之间的多对多关系,使两张数据表关联关联起来,一般是通过生成一张映射表来实现这种映射关系。关于上面的cascade属性和fetch属性,有兴趣的读者可以查资料了解。 - @JoinTable
这个注解是配套@ManyToMany使用的,一般在多对多关系的维护端标注,用于生成上面提到的映射表。一般该注解常用三个属性:name属性表示生成的数据表的名称,joinColumns属性表示关系维护端的主键,inverseJoinColumns则表示关系被维护端的主键。关于嵌套在里面的@JoinColumn注解,在这里主要用于配置映射表的外键,一般有两个属性:name用于配置外键在映射表中的名称,referencedColumnName 用于表明外键在原表中的字段名称。 - @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。
踩到的坑
- 涉及到两张表以上的更新或者删除操作,为了保证数据库的一致性,需要添加 @Transactional事务注解,否则程序会抛出异常。(关于事务的详情,如果不熟悉的话,强烈建议去弄懂。)
- 如果要执行删除操作,需要先把它的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层返回的数据,最后返回具体的页面和数据到客户端。
本层注解简单解释:
-
@RestController
Spring4之后新加入的注解,相当于@Controller + @ResponseBody。
@Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。当然也有语义化的作用,即代表该类是充当Controller的作用
@ResponseBody 它的作用简单来说说就是指该类中所有的API接口返回的数据,甭管你对应的方法返回Map或是其他Object,它会以Json字符串的形式返回给客户端,根据尝试,如果返回的是String类型,则仍然是String。 -
@RequestMapping("/user")
该注解用来处理请求地址的映射,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。 -
@Autowired
养成看源代码的好习惯,在IDEA中按住Ctrl键点击该注解,可以查看该注解的解析。我理解了一下,大概就是调用这个类的构造方法对这个类进行实例化操作。 -
@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.cnblogs.com/hhhshct/p/9492741.html
- https://liuyanzhao.com/7913.html
- https://blog.csdn.net/lidai352710967/article/details/83509821
- https://blog.csdn.net/H_Shun/article/details/78013017
- https://blog.csdn.net/Just_learn_more/article/details/90665009
- https://www.sojson.com/blog/295.html
- https://www.jianshu.com/p/6bbb5748ac83
- 点赞 9
- 收藏
- 分享
- 文章举报
- 是时候该开始读一读JDK源码了
- 啃透JDK源码-JCheckBox详解
- Java编程思想第五版(On Java8)读书笔记(一)-什么是对象
- JavaSwing-AbstractButton详解
- JavaSwing-ItemListener接口
- JavaSwing-ItemSelectable
- 写给大忙人看的 Java 基础知识
- 【JAVA Lambda】初学者对Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)套娃的理解
- spring的注解(wjw)
- 【设计模式】抽象工厂模式--------java
- SpringBoot启动时Caused by: java.net.BindException: Address already in use: bind
- spring+mybatis所需jar包
- 【设计模式】简单工厂、工厂方法模式的实现--------java
- java调用azkaban接口
- Java语言开发工具 v2020.1汉化版
- Java实现称重3次找到假球
- springboot最简单的搭建与分析
- java生成验证码图片
- springboot+Mybatis依赖+各种报错
- springboot简单的搭建+增加功能(热部署)