首先来说一下权限数据库的设计
RBAC是一种访问控制模型,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
1、hr表是用户表,存放了用户的基本信息
2、role表是角色表,根据springsecurity的规范,里面有个name字段表示角色的英文名称。
3、menu表是一张资源表,登陆成功后根据用户的动态加载需要的模块,用户表里面有个url字段,表示一个url pattern,根据路径匹配规则,发出一个admin/user的请求,会被admin/**拦截到,系统再去查看这规则对应的角色是哪些,然后再去查看改用户是否具备相应的角色,进而判断请求是否合法
说到判断用户的角色,就要说到动态处理角色和资源的关系了
用户认证通过Provider来做,所以Provider需要拿到系统已经保存的认证信息,其中UserDetails用来拿到用户的信息,有几个方法需要在Hr类中实现,其中一个是isenable方法判断用户是不是被禁用,还有一个方法叫做getAuthorities,这方法用来获取当前用户所具有的角色
注:即直接从 roles 中获取当前用户所具有的角色,构造 SimpleGrantedAuthority 然后返回即可。
UserDetails
从上面可以知道最终交给Spring Security的是。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象中去。默认提供了:
用户的权限集, 默认需要添加前缀用户的加密后的密码,
不加密会使用前缀
应用内唯一的用户名
账户是否过期
账户是否锁定
凭证是否过期
用户是否可用
在配置类中添加了两个自定义拦截器 JwtLoginFilter
和 JwtTokenFilter,JwtLoginFilter
继承了 UsernamePasswordAuthenticationFilter
理表单提交的身份信息。 封装了凭证后进行登陆信息的校验。用户认证的管理类,所有的认证请求(比如login)都会通过提交一个token给AuthenticationManager
的authenticate()
方法来实现, 跳转到 JwtAuthenticationProvider.authenticate
中进行逻辑处理,校验成功则会调用 JwtLoginSuccessHandler
.生成一个token并返回给用户。
JwtTokenFilter
判断当前请求的角色是否有访问当前路径的权限,校验当前用户是否具备所需要的角色。校验通过,则允许访问,否则抛出 AccessDeniedException 异常。
然后再自定义权限拦截FileterInvocationSecurityMetadataSource有一个默认的实现类DefaultFilterInvocationSecurityMetadataSource。功能就是通过当前的请求地址,获取该地址需要的用户角色
自定义 UrlAccessDecisionManager 类实现 AccessDecisionManager 接口 访问决策管理器 判断当前请求的角色是否有访问当前路径的权限
配置webSecurityConfig
- 在 configure(HttpSecurity http) 方法中,将刚刚创建的 权限拦截,访问决策管理器 ,注入进来。到时候,请求都会经过刚才的过滤器(除了 configure(WebSecurity web)方法忽略的请求)。
- successHandler 中配置登录成功时返回的 JSON,登录成功时返回当前用户的信息。
- failureHandler 表示登录失败,登录失败的原因可能有多种,我们根据不同的异常输出不同的错误提示即可。
登录和注册都要密码加密加盐 通过 BCryptPasswordEncoder 中的 encode 方法对密码进行处理
部门的数据库设计和存储过程
DELIMITER $$ //设置存储结构的结束符号
USE `vhr`$$ //使用vhr库
DROP PROCEDURE IF EXISTS `addDep`$$ //如果存在删除
CREATE DEFINER=`root`@`localhost` PROCEDURE `addDep`(in depName varchar(32),in parentId int,in enabled boolean,out result int,out result2 int)
begin
declare did int; //定义did整数型
declare pDepPath varchar(64); //pdeppath varchar
insert into department set name=depName,parentId=parentId,enabled=enabled; //插入name id enable
select row_count() into result; //返回修改的行数
select last_insert_id() into did; //最后插入的id
set result2=did; //刚刚的id赋值给result2
select depPath into pDepPath from department where id=parentId; //查找父类depath值赋给pdepath
update department set depPath=concat(pDepPath,'.',did) where id=did;//设置depath的值
update department set isParent=true where id=parentId; //让父类的isparent为true
end$$
DELIMITER ;
- 该存储过程接收五个参数,三个输入参数分别是部门名称、父部门 Id,该部门是否启用,两个输出参数分别表示受影响的行数和插入成功后 id 的值。
- 存储过程首先执行插入操作,插入完成后,将受影响行数赋值给 result。
- 然后通过
last_insert_id()
获取刚刚插入的 id,赋给 result2。 - 接下来查询父部门的 depPath,并且和刚刚生成的id组合后作为刚刚插入部门的 depPath。
- 将父部门的 isParent 字段更新为 true。
删除部门的存储结构
DELIMITER $$
USE `vhr`$$
DROP PROCEDURE IF EXISTS `deleteDep`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `deleteDep`(in did int,out result int)
begin //输入 did 输出 result
declare ecount int; //定义ecout
declare pid int; //定义pid
declare pcount int; //定义pcount
select count(*) into ecount from employee where departmentId=did; //ecount=did的数量
if ecount>0 then set result=-1; //如果 ecount>0则result=-1
else
select parentId into pid from department where id=did; //设置pid=父部门
delete from department where id=did and isParent=false; //删除id和is设置为fasle的条
select row_count() into result; //返回result受影响的行数
select count(*) into pcount from department where parentId=pid; //返回pcount=夫部门的行数
if pcount=0 then update department set isParent=false where id=pid; //如果父部门为0 然后设置 isparent和设置 id=
end if;
end if;
end$$
DELIMITER ;
- 一个输入参数表示要删除数据的 id,一个输出参数表示删除结果。
- 如果该部门下有员工,则该部门不能被删除。
- 删除该部门时注意加上条件 isParent=false,即父部门不能被删除,这一点我在前端已经做了判断,正常情况下父部门的删除请求不会被发送,但是考虑到前端的数据不能被信任,所以后台我们也要限制。
- 删除成功之后,查询删除部门的父部门是否还有其他子部门,如果没有,则将父部门的 isParent 修改为 false。
整合RabbitMq实现邮件发送
1.用户提交注册信息后,将消息提交给队列,让后队列将发送邮件通知给用户
导入rabbitmq依赖和mail依赖。thymeleaf依赖,server依赖,在添加员工的实现类
然后再rabbitmqconfig里面配置消息队列和topic
交换机
生产者
1.Producer
先连接到Broker,建立连接Connection,开启一个信道(Channel)。
2.Producer
声明一个交换器并设置好相关属性。
3.Producer
声明一个队列并设置好相关属性。
4.Producer
通过路由键将交换器和队列绑定起来。
5.Producer
发送消息到Broker
,其中包含路由键、交换器等信息。
6.相应的交换器根据接收到的路由键查找匹配的队列。
7.如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者。
8.关闭信道。
消费者
1.Producer
先连接到Broker
,建立连接Connection
,开启一个信道(Channel
)。
2.向Broker
请求消费响应的队列中消息,可能会设置响应的回调函数。
3.等待Broker
回应并投递相应队列中的消息,接收消息。
4.消费者确认收到的消息,ack
。
5.RabbitMq
从队列中删除已经确定的消息。
6.关闭信道。
7.关闭连接。
9.管理连接。
然后在添加员工的实现类添加邮件通知。
最后添加监听邮件,如果邮件发送失败的话,会重新入队
easyPOI导入和到处excel文件
在employee实体类中添加excel注解。其中有个nation民族的类里面有id属性,然后就写mapper查询员工,增加导出员工数据的接口,以流的形式输出
导入员工数据
导入员工数据也是以io的数据传入的,用list遍历增加员工的数据,最后使用savebatch比较列表是不是添加成功的。