??代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客
之前只零碎的学习过spring-cloud-alibaba,并没有全面了解过,这次学习pig框架时,想着可以根据这个项目学习一下,练练手,于是断断续续的用了几天时间搭建了一下基础框架。目前就先重点记录一下遇到的问题吧,毕竟流程也不是特别复杂,就是有的东西没遇到过了解的也不深~
由于微服务包括认证这里内容太多,所以分了好几篇~
第一篇文章:No6.从零搭建spring-cloud-alibaba微服务框架,实现fegin、gateway、springevent等(一)_清晨敲代码的博客-CSDN博客
文章包括:
1.将服务系统注册到nacos注册中心;
2.通过nacos实现配置动态更新;
3.添加fegin服务,实现服务之间调用;
4.添加网关(学会使用webflux,学会添加过滤器);
5.添加log服务,通过springevent实现,并使用注解使用(使用AOP);
第二篇文章:
No6.从零搭建spring-cloud-alibaba微服务框架,实现数据库调用、用户认证与授权等(二,no6-2)_清晨敲代码的博客-CSDN博客
文章包括:
6.添加 mysql 数据库调用,并使用mybatis-plus操作;
7.在认证模块添加用户认证,基于oauth2的自定义密码模式(已认证用户是基于自定义token加redis持久化,不是session);
第三篇文章:
No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)
8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑(但是没有处理微服务间的不鉴权调用,微服务间的调用接口都是白名单呢!);
第三篇文章:
No6-4.从零搭建spring-cloud-alibaba微服务框架,解决微服务间的不鉴权调用等(四,no6-4)
9.解决微服务间的不鉴权调用(可修改外部访问鉴权逻辑~)
本篇内容包括:
10.添加用户鉴权逻辑,操作权限(基于RBAC逻辑,使用springsecuiry安全注解实现)
剩余包括(会有变动):
11.添加用户鉴权逻辑,数据权限(基于RBAC逻辑,使用springsecuiry安全注解实现)
目录
A10.添加用户鉴权逻辑(基于RBAC逻辑,使用springsecuiry安全注解实现)
具体业务(只关注重点难点的,增删改查看pig项目就好,就不详说了代码都能懂):
数据结构:
代码实现步骤【这里就不贴代码了,太多了,可以回到顶部看gitee里对应分支的代码】:
测试:
遇到的问题:
1.FIND_IN_SET(str,strlist)
?2.cn.hutool.core.lang.tree.TreeUtil?
3.mps 的 分页查询 IPage
4.mps的collection的两种方式
5.@RestControllerAdvice拦截不到抛出的异常!
A10.添加用户鉴权逻辑(基于RBAC逻辑,使用springsecuiry安全注解实现)
注意:由于pig项目开源版没有提供数据权限,所以A10中只有操作权限逻辑,在后面会添加数据权限的
RBAC(Role-Based Access Control)的逻辑就不重复了,可以看这篇文章:什么是 RBAC 模型?
总的来说就是基于角色的访问控制。通过用户关联角色,角色关联权限,来间接的为用户赋予权限。
我们现在有用户信息了,只需要添加上角色信息,权限信息即可,在pig项目中权限信息的操作权限由目录、菜单、按钮的操作来代替;数据权限是由部门来代替的。
我们开发的逻辑就需要提前理清楚,首先先要知道具体的业务是什么,然后要根据业务整理数据结构是什么,然后再将业务和数据通过代码体现出来。
具体业务(只关注重点难点的,增删改查看pig项目就好,就不详说了代码都能懂):
1.菜单信息增删改查;
2.部门信息增删改查(维护组织架构所需要的,同时也是用户数据权限所需要的);
3.角色信息增删改查;其中会包括获取菜单列表(里面就包含目录、菜单、按钮的操作);获取部门列表
4.用户信息增删改查;其中会包括获取角色列表、部门列表;登录时还会根据角色获取权限列表;
5.最后查询列表时,还要根据数据权限;(A10.先实现前五个)?
数据结构:

代码实现步骤【这里就不贴代码了,太多了,可以回到顶部看gitee里对应分支的代码】:
1.先创建数据库表;
2.添加与数据表对应的实体类;
* 在此时整理接口文档,把接口、入参、出参整理完毕;
3.添加菜单和部门的基础增删改查;期间添加获取菜单下拉列表和部门下拉列表的逻辑;
4.添加角色的增删改查,同时需要添加角色-菜单表的业务类和mapper接口;期间添加获取角色列表的逻辑;
5.修改用户VO、DTO类,原来并没有添加权限的相关内容;
6.添加用户-角色表的操作逻辑,修改用户的增删改查,添加用户的角色部门权限等内容;
7.添加登录成功后获取用户菜单列表的方法;
* 在此时测试接口是否正常使用
8.添加 security 注解 @PreAuthorize 需要的鉴权的类;
9.如果@PreAuthorize 中鉴权失败接口就调用失败,会抛出?AccessDeniedException 异常,后端要返回清晰地说明,那么我们就添加全局异常处理~
-- 1.就使用 pig 项目数据库中的五张表就行,看下面表明;注意,pig 表的id都不是自增,并且在实体代码中写的是用 mybatis-plus的IdType.ASSIGN_ID生成的,可以我看数据库中的id格式并不是按照这个,所以就修改为使用数据库自增策略(查不到MybatisPlus 主键策略使用场景,后续再了解详情吧)
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL COMMENT '菜单名称',
`permission` varchar(32) DEFAULT NULL COMMENT '菜单权限标识,用于后端接口鉴权标识',
`path` varchar(128) DEFAULT NULL COMMENT '前端URL,用于vue前端页面路径',
`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID',
`icon` varchar(32) DEFAULT NULL COMMENT '图标,用于前端菜单、按钮显示的图标',
`sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序值',
`keep_alive` char(1) DEFAULT '0' COMMENT '用于前端页面是否开启缓存,0-开启,1- 关闭',
`type` char(1) DEFAULT NULL COMMENT '菜单类型 (0菜单 1按钮)',
`del_flag` char(1) DEFAULT '0' COMMENT '逻辑删除标记(0--正常 1--删除)',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='菜单权限表';
CREATE TABLE `sys_dept` (
`dept_id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父级id',
`name` varchar(50) DEFAULT NULL COMMENT '部门名称',
`ancestors` varchar(100) DEFAULT NULL COMMENT '所有祖级id包括自己,逗号隔开',
`sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
`del_flag` char(1) DEFAULT '0' COMMENT '是否删除 1:已删除 0:正常',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='部门管理';
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(64) NOT NULL,
`role_code` varchar(64) NOT NULL COMMENT '角色标识,用于接口判断角色权限的',
`role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
PRIMARY KEY (`role_id`),
UNIQUE KEY `role_idx1_role_code` (`role_code`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统角色表';
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL,
`menu_id` bigint(20) NOT NULL,
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='角色菜单表';
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户角色表';
-- 这里缺少一个 sys_role_dept 表,pig 里面是没有的,我们后续追加~
//2.根据那五张表添加实体类,SysDept、SysMenu、SysRole 继承自定义的实体基类 BaseEntity,SysRoleMenu、SysUserRole继承 mybatisplus 的 Model 类
//其中 id 要添加 @TableId(value = "menu_id", type = IdType.AUTO) 注解,并且id要数据库自动生成的
//其中 delFlag 删除标识,要添加 @TableLogic(value = "1", delval = "0") 注解,这样能使用 mps 的逻辑删除和逻辑查询,默认值和删除值也可以通过配置设置。【该注解只对自动注入的 sql 起效,自己在mapper.xml中写的sql不生效】
//拿 SysDept 举例
@Data
@EqualsAndHashCode(callSuper = true)
public class SysDept extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId;
/**
* 部门名称
*/
private String deptName;
/**
* 排序
*/
private Integer sortOrder;
/**
* 父级部门id
*/
private Long parentId;
/**
* 所有祖级id包括自己,逗号隔开
*/
private String ancestors;
/**
* 是否删除 -1:已删除 0:正常
*/
@TableLogic
private String delFlag;
}
//3.添加菜单和部门的基础增删改查;
//(1)这里的 service 类中的增删改查使用 mps 自带的,也就是 ServiceImpl 自带的 save()/update()等方法;为了方便记忆,我们自定义的方法名都要带有实体类名字,那么只要方法名中没有带有实体类名就是 mps 提供的【以后用多了就记住了】
//(2)按条件查询时使用 Wrappers 方式
//(3)如果有需要树形结构,使用 hutool 的 TreeUtil 方式,这个很方便,而且可以自由追加属性~
//4.添加角色的增删改查,同时需要添加角色-菜单表的业务类和mapper接口;
//(1)角色里面包含了操作权限和数据权限和角色基本内容,由于角色的操作权限和数据权限关乎用户的权限,而角色基本内容是不会的,所以就将这两个操作分开,并且当修改权限的时候,清除已缓存的认证用户信息和用户的菜单权限【未开启缓存可以不用清除】;修改角色基本信息的时候不用清除;而这就需要用到操作角色-部门、角色-菜单关联表操作【这里先不操作角色-部门,留到后面数据权限时操作】;
//(2)所以对于角色业务来说,增改查业务都是操作角色表,角色权限业务是操作角色-菜单表,而删除角色是操作这两张表
//(3)由于角色的基础增改查都是单表,所以不用重写 service 方法,直接在controller中使用 mps 就行;删除和保存角色的权限时,就需要涉及到两个表操作了,记得加上 @Transactional 注解,防止 sql 语句报错能进行回滚【部门和菜单的其实也可以和角色一样的~】
//5.修改用户VO、DTO类,原来并没有添加权限的相关内容;
//这里原来按照 pig 项目设计时,除用户实体类外,还有两个dto和两个vo类,但是使用逻辑有些乱,所以我又重新梳理了一下,
//除实体类外,我们还需要前端传值用于增改等的UserDTO(包含用户基本和权限、角色),还需要用户业务查询的UserVO(包含用户基本的和角色),还需要用户认证信息的UserInfoVO(包含用户基本和权限、角色);
//6.添加用户-角色表的操作逻辑,修改用户的增删改查,添加用户的角色部门权限等内容;
//(1)增删改查都要涉及到用户-角色表的关联操作;例如添加用户时,需要同时保存关联的用户-角色表,这里就需要给业务方法添加 @Transactional 注解,当出现异常是可以回滚~
//(2)其中分页查询需要查询出角色信息,这就涉及到表关联查询;可以使用 mps 的 collection 关联查询
//(3)由于重改了一下用户信息的dto/vo表,记得将所有涉及到的都修改!!!
//(4)用户登录成功回哪到token,然后根据token获取自己的用户及权限,且认证用户的权限permissions只查询按钮的权限;而菜单和目录的权限是单独查询的;
//(5)还需要在PigUserDetailsService里面设置上角色和权限
这里会用到 SysUserMapper.xml SysRoleMapper.xml SysMenuMapper.xml ~
//7.添加登录成功后获取用户菜单列表的方法;
//(1)当前菜单分为顶部菜单和左侧菜单,可以理解为顶部为一级,左侧为二级,然后后以此类推。这样的话,用户认证成功后,获取菜单权限可以分好几种:1.返回全部权限,就是根据角色搜索到用户所有的权限;2.返回默认的一个顶部的二级左侧权限,和顶部所有的一级列表。
//第一种通过一个接口入参parentid,仅查询当前用户权限祖级包含parentid就可以,只过滤掉按钮类型;第二种是在第一种基础上过滤掉按钮和顶级菜单,并追加了一个返回顶部一级菜单列表的接口;
//我们这例只实现第一种的。
//8.添加 security 注解 @PreAuthorize 需要的鉴权的类;
//(1)我们使用 security 提供的基于注解鉴权的方式,首先要开启注解鉴权,先前在 @EnablePigResourceServer 注解里面已经设置为开启了:@EnableGlobalMethodSecurity(prePostEnabled = true);
//(2)然后在需要鉴权的方法上面加上 @PreAuthorize("@pms.hasPermission('sys_user_add')") 注解,其中 'sys_user_add' 填写能访问该方法的权限唯一标识集合【权限标识也就是菜单表里面】;
//(3)创建鉴权类,逻辑就是拿到可访问该方法的标识集合,然后判断当前用户是否有这些标识,如果有则直接通过;如果没有则会抛出异常 AccessDeniedException。记得将该类注入到容器中
测试:
写到这里,就可以测试一下了,先创建一个账号,然后分角色和权限,然后分别访问有权限的接口和没权限的接口,有权限的接口就可以正常访问,没权限的接口会返回 403 。


最后,可以看到控制台已经抛出了?AccessDeniedException 异常,如果我们不想显示异常,可以添加全局异常处理类~【这里先简单添加】
//9.如果@PreAuthorize 中鉴权失败接口就调用失败,会抛出?AccessDeniedException 异常,后端要返回清晰地说明,那么我们就添加全局异常处理~
//(1)创建一个类 GlobalExceptionHandle ,添加 @RestControllerAdvice 注解,然后创建方法,入参是要捕获的异常类,并给方法加上 @ExceptionHandler(AccessDeniedException.class) 注解,参数是要捕获的异常类;
//举个例子:
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice
public class GlobalExceptionHandle {
public GlobalExceptionHandle(){
System.out.println();
}
/**
* @Description: AccessDeniedException
* @param e
* @Return: com.pig4cloud.pig.common.core.util.R
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public R handleAccessDeniedException1(AccessDeniedException e) {
String msg = "AbstractAccessDecisionManager.accessDenied"+ e.getMessage();
log.warn("拒绝授权异常信息 ex={}", msg);
return R.failed(e.getLocalizedMessage());
}
}
然后再次访问未授权的方法,就可以看到拦截并返回的数据啦
并且还没有报错信息


遇到的问题:
1.FIND_IN_SET(str,strlist)
FIND_IN_SET(str,strlist),该函数的作用是查询字段(strlist) 中是否包含(str)的结果,返回结果为 null或记录 。
str 要查询的字符串,注意 str 前后不用加逗号~
strlist 需查询的字段,参数以”,”分隔,形式如 (1,2,6,8,10,22)
作用:返回树形结构类型,默认有 id、parentId、name、weight,可以追加属性,最终会生成如下的格式:
注意:使用TreeUtil.build(collect, parentid)时,只会拿到从parentid开始的列表!!!
{
//这四个是我们提供给 TreeNode 的属性的
"id": "8",
"parentId": "0",
"weight": 62,
"name": "法整活场",
//这两个是扩展属性,放到 map 里面的
"ancestors": "0,",
"createTime": "2022-11-01 07:44:18",
//如果有子级,就一定会有的属性
"children": [
{
"id": "9",
"parentId": "8",
"weight": 33,
"name": "只111增属证",
"ancestors": "0,8,",
"createTime": "2022-11-01 07:45:32"
}
]
}
3.mps 的 分页查询 IPage
前端传分页数据:当前页码,多少条,排序方式等信息,然后赋值给 Page 对象,之后调用并将page传值给?mps 自带的service、mapper的 page()方法,最终的结构就是:
{
"records": [
{...}
],
"total": 5,
"size": 3,
"current": 1,
"orders": [],
"optimizeCountSql": true,
"searchCount": true,
"countId": null,
"maxLimit": null,
"pages": 2
}
4.mps的collection的两种方式
一种是一个 sql 查询,但是多表时查询速度较慢;见SysUserMapper.xml里面的resultMap id="userVoResultMap"
一种是多个sql查询出需要的数据。主要用于关联多表查询,提升查询速度;见SysUserMapper.xml里面的resultMap id="baseResultMap";
5.@RestControllerAdvice拦截不到抛出的异常!
前提是:该全局异常已添加到容器中
一切都准备好了但就是捕获不到 AccessDeniedException 异常!!!!
最后发现?@ExceptionHandler(AccessDeniedException.class) 里面的AccessDeniedException异常 import 的是?java.nio.file.AccessDeniedException 类的,我裂开了o(▼皿▼メ;)o
|