springboot权限验证学习-下

上篇讲了rbac对于菜单的权限,下面准备完成按钮权限以及行数据和列数据权限

权限控制(按钮权限)

权限控制

操作权限就是将操作视为资源,比如删除操作,有些人可以有些人不行。于后端来说,操作就是一个接口。于前端来说,操作往往是一个按钮,所以操作权限也被称为按钮权限,是一种细颗粒权限。
在页面上比较直观的体现就是没有这个删除权限的人就不会显示该按钮,或者该按钮被禁用

前端实现按钮权限还是和之前导航菜单渲染一样的,拿当前用户的权限资源id和权限资源字典对比,有权限就渲染出来,无权限就不渲染。

前端关于权限的逻辑和之前一样,那操作权限怎么就比页面权限安全了呢?这个安全主要体现在后端上,页面渲染不走后端,但接口可必须得走后端,那只要走后端那就好办了,我们只需要对每个接口进行一个权限判断就OK了嘛。

资源表增加type类型,0表示页面权限,1表示操作权限
表扩展完毕,我们接下来就要添加操作权限类型的数据。刚才也说了,于后端而言操作就是一个接口,那么我们就要将 接口路径 作为我们的权限资源,看一下数据就清楚了。

alter table resource add type int comment '资源类型';
update resource set type=0;
insert into resource (path,name,type) value('DELETE:/api/user','删除用户',1);
-- 超级管理员添加权限
insert into role_resource (roleId,resourceId) value(1,5);

修改resource

@Data
public class Resource {private int id;private String name;private String path;private int type;
}

修改resourcemapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wujialiang.auth.mapper.ResouceMapper"><select id="getCurrentUserMenus" parameterType="java.lang.String" resultType="com.wujialiang.auth.entity.Resource">select t2.id,t2.name,t2.path,t2.type from role_resource t1left join resource t2 on t2.id=t1.resourceIdwhere t1.roleId =(select t3.roleId from user_role t3 where t3.userId=(select t4.id from user t4 where t4.UserName=#{userName} limit 1)limit 1)</select>
</mapper>

超级管理员权限
在这里插入图片描述
数据管理员就没有
在这里插入图片描述
DELETE:/API/user分为两个部分组成,DELETE:表示该接口的请求方式,比如GETPOST等,/API/user则是接口路径了,两者组合起来就能确定一个接口请求!

数据有了,我们接着在代码中进行权限安全判断,因为只有前端基本和裸奔一样!!
修改userservice接口

/*** 获取全部的菜单* @return*/
public List<Resource> getAllMenus(){return resouceMapper.getAllMenus();
}

修改ResouceMapper

    /*** 获取全部的菜单* @return*/List<Resource> getAllMenus();

修改ResouceMapper.xml

    <select id="getAllMenus" resultType="com.wujialiang.auth.entity.Resource">select t2.id,t2.name,t2.path,t2.type from resource t2</select>

修改userController

/*** 删除用户测试接口* @return*/
@DeleteMapping("/api/user")
public String deleteUser() {// 拿到所有权限路径 和 当前用户拥有的权限路径List<Resource> allMenuss = userService.getAllMenus();List<Resource> userMenus = userService.getCurrentUserMenus();List<String> userPaths = userMenus.stream().map(Resource::getPath).collect(Collectors.toList());List<String> allPaths = allMenuss.stream().map(Resource::getPath).collect(Collectors.toList());// 第一个判断:所有权限路径中包含该接口,才代表该接口需要权限处理,所以这是先决条件,如果权限路径中没有则放行// 第二个判断:判断该接口是不是属于当前用户的权限范围,如果不是,则代表该接口用户没有权限if (allPaths.contains("DELETE:/api/user") && !userPaths.contains("DELETE:/api/user")) {return "您没权限操作";}return "操作成功";
}

没有登陆
在这里插入图片描述
用户1超级管理员登录
在这里插入图片描述
用户2数据管理员
在这里插入图片描述
这样即使知道接口也没权限,保证了接口安全

接口扫描

pringMVC提供了一个非常方便的类RequestMappingInfoHandlerMapping,这个类可以拿到所有你声明的web接口信息,这个拿到后剩下的事不就非常简单了,就是通过代码将接口信息批量添加到数据库呗!不过我们也不是要真的将所有接口都添加到权限资源中去,我们要的是那些需要权限处理的接口生成权限资源,有些接口不需要权限处理那自然就不生成了。所以我们得想一个办法来标记一下该接口是否需要被权限管理!
定义一个注解Auth

package com.wujialiang.auth.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE}) // 表明该注解可以加在类或方法上
public @interface Auth {/*** 权限id,需要唯一*/int id();/*** 权限名称*/String name();
}

新建UserTestConroller

package com.wujialiang.auth.controller;import com.wujialiang.auth.annotation.Auth;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/API/usertest")
@Auth(id = 1000, name = "用户管理")
public class UserTestConroller {@PostMapping@Auth(id = 1, name = "新增用户")public String createUser() {return "操作成功";}@DeleteMapping@Auth(id = 2, name = "删除用户")public String deleteUser() {return "操作成功";}@PutMapping@Auth(id = 3, name = "编辑用户")public String updateRoles() {return "操作成功";}@GetMapping("/test/{id}")@Auth(id = 4,name = "用于演示路径参数")public String testInterface() {return "操作成功";}
}

可以看到,上面代码中我在类和方法上都加上了我们自定义的Auth注解,并在注解中设置了id和name的值,这个name好理解,就是资源数据中的资源名称嘛。可注解里为啥要设计id呢,数据库主键id不是一般都是用自增嘛。这是因为我们人为控制资源的主键id有很多好处。

首先是id和接口路径的映射特别稳定,如果要用自增的话,我一个接口一开始的权限id是4,一大堆角色绑定在这个资源4上面了,然后我业务需求有一段时间不需要该接口做权限管理,于是我将这个资源4删除一段时间,后续再加回来,可数据再加回来的时候id就变成5,之前与其绑定的角色又得重新设置资源,非常麻烦!如果这个id是固定的话,我将这个接口权限一加回来,之前所有设置好的权限都可以无感知地生效,非常非常方便。所以,id和接口路径的映射从一开始就要稳定下来,不要轻易变更!

至于类上加上Auth注解是方便模块化管理接口权限,一个Controller类咱们就视为一套接口模块,最终接口权限的id就是模块id + 方法id。大家想一想如果不这么做的话,我要保证每一个接口权限id唯一,我就得记得各个类中所有方法的id,一个一个累加地去设置新id。比如上一个方法我设置到了101,接着我就要设置102、103…,只要一没注意就设置重了。可如果按照Controller类分好组后就特别方便管理了,这个类是1000、下一个类是2000,然后类中所有方法就可以独立地按照1、2、3来设置,极大避免了心智负担!

介绍了这么久注解的设计,我们再讲解接口扫描的具体实现方式!这个扫描肯定是发生在我新接口写完了,重新编译打包重启程序的时候!并且就只在程序启动的时候做一次扫描,后续运行期间是不可能再重复扫描的,重复扫描没有任何意义嘛!既然是在程序启动时进行的逻辑操作,那么我们就可以使用SpringBoot提供的ApplicationRunner接口来进行处理,重写该接口的方法会在程序启动时被执行。(程序启动时执行指定逻辑有很多种办法,并不局限于这一个,具体使用根据需求来)

我们现在就来创建一个类实现该接口,并重写其中的run方法,在其中写上我们的接口扫描逻辑。注意,下面代码逻辑现在不用每一行都去理解,大概知道这么个写法就行,重点是看注释理解其大概意思,将来再慢慢研究:

package com.wujialiang.auth.component;import com.wujialiang.auth.annotation.Auth;
import com.wujialiang.auth.entity.Resource;
import com.wujialiang.auth.service.UserService;
import io.jsonwebtoken.lang.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;@Component
public class ApplicationStartup implements ApplicationRunner {@Autowiredprivate RequestMappingInfoHandlerMapping requestMappingInfoHandlerMapping;//    @Autowired
//    private UserService userService;@Overridepublic void run(ApplicationArguments args) throws Exception {// 扫描并获取所有需要权限处理的接口资源(该方法逻辑写在下面)List<Resource> list = getAuthResources();// 先删除所有操作权限类型的权限资源,待会再新增资源,以实现全量更新(注意哦,数据库中不要设置外键,否则会删除失败)//resourceService.deleteResourceByType(1);// 如果权限资源为空,就不用走后续数据插入步骤if (Collections.isEmpty(list)) {return;}for (Resource resource : list) {System.out.print("\tid:"+resource.getId());System.out.print("\tpath:"+resource.getPath());System.out.print("\tname:"+resource.getName());System.out.print("\ttype:"+resource.getType());System.out.print("\n");}// 将资源数据批量添加到数据库//resourceService.insertResources(list);System.out.println("将资源数据批量添加到数据库成功!!!");}/*** 扫描并返回所有需要权限处理的接口资源*/private List<Resource> getAuthResources() {// 接下来要添加到数据库的资源List<Resource> list = new LinkedList<>();// 拿到所有接口信息,并开始遍历Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingInfoHandlerMapping.getHandlerMethods();handlerMethods.forEach((info, handlerMethod) -> {// 拿到类(模块)上的权限注解Auth moduleAuth = handlerMethod.getBeanType().getAnnotation(Auth.class);// 拿到接口方法上的权限注解Auth methodAuth = handlerMethod.getMethod().getAnnotation(Auth.class);// 模块注解和方法注解缺一个都代表不进行权限处理if (moduleAuth == null || methodAuth == null) {return;}// 拿到该接口方法的请求方式(GET、POST等)Set<RequestMethod> methods = info.getMethodsCondition().getMethods();// 如果一个接口方法标记了多个请求方式,权限id是无法识别的,不进行处理if (methods.size() != 1) {return;}// 将请求方式和路径用`:`拼接起来,以区分接口。比如:GET:/user/{id}、POST:/user/{id}String path = methods.toArray()[0] + ":" + info.getPatternsCondition().getPatterns().toArray()[0];// 将权限名、资源路径、资源类型组装成资源对象,并添加集合中Resource resource = new Resource();resource.setPath(path);resource.setType(1);resource.setName(methodAuth.name());resource.setId(moduleAuth.id() + methodAuth.id());list.add(resource);});return list;}
}

在这里插入图片描述
现在是核心逻辑 + 接口扫描,不过还不够。现在我们每一个权限安全判断都是写在方法内,且这个逻辑判断代码都是一样的,我有多少个接口需要权限处理我就得写多少重复代码,这太恶心了。

拦截器

拦截器中的代码和之前接口方法中写的逻辑判断大致一样,还是一样,看注释理解大概思路即可:

package com.wujialiang.auth.interceptor;import com.wujialiang.auth.entity.Resource;
import com.wujialiang.auth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate UserService resourceService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果是静态资源,直接放行if (!(handler instanceof HandlerMethod)) {return true;}// 获取请求的最佳匹配路径,这里的意思就是我之前数据演示的/API/user/test/{id}路径参数// 如果用uri判断的话就是/API/user/test/100,就和路径参数匹配不上了,所以要用这种方式获得String pattern = (String)request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);// 将请求方式(GET、POST等)和请求路径用 : 拼接起来,等下好进行判断。最终拼成字符串的就像这样:DELETE:/API/userString path = request.getMethod() + ":" + pattern;// 拿到所有权限路径 和 当前用户拥有的权限路径// 拿到所有权限路径 和 当前用户拥有的权限路径List<Resource> allMenuss = resourceService.getAllMenus();List<Resource> userMenus = resourceService.getCurrentUserMenus();List<String> userPaths = userMenus.stream().map(Resource::getPath).collect(Collectors.toList());List<String> allPaths = allMenuss.stream().map(Resource::getPath).collect(Collectors.toList());// 第一个判断:所有权限路径中包含该接口,才代表该接口需要权限处理,所以这是先决条件,// 第二个判断:判断该接口是不是属于当前用户的权限范围,如果不是,则代表该接口用户没有权限if (allPaths.contains(path) && !userPaths.contains(path)) {System.out.println("您没有权限访问");return false;}// 有权限就放行return true;}
}

截器类写好之后,别忘了要使其生效,这里我们直接让SpringBoot启动类实现WevMvcConfigurer接口来做

package com.wujialiang.auth;import com.wujialiang.auth.interceptor.AuthInterceptor;
import com.wujialiang.auth.interceptor.LoginInterceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 网站入口**/
@SpringBootApplication
public class App implements WebMvcConfigurer {public static void main(String[] args)  {// 第一个参数是该类的名字.class 第二个参数是main方法中的参数SpringApplication.run(App.class, args);}@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器使拦截器生效registry.addInterceptor(new LoginInterceptor());// 添加权限拦截器,并排除登录接口(如果有登录拦截器,权限拦截器记得放在登录拦截器后面)registry.addInterceptor(authInterceptor()).excludePathPatterns("/login");}// 这里一定要用如此方式创建拦截器,否则拦截器中的自动注入不会生效@Beanpublic AuthInterceptor authInterceptor() {return new AuthInterceptor();};
}

注意,拦截器中获取权限数据现在是直接查的数据库,实际开发中一定一定要将权限数据存在缓存里(如Redis),否则每个接口都要访问一遍数据库,压力太大了!这里为了减少心智负担,就不整合Redis了。

插入测试数据

insert into resource (id,path,name,type) value(1001,'POST:/API/usertest','新增用户',1);
insert into resource (id,path,name,type) value(1002,'DELETE:/API/usertest','删除用户',1);
insert into resource (id,path,name,type) value(1003,'PUT:/API/usertest','编辑用户',1);
insert into resource (id,path,name,type) value(1004,'GET:/API/usertest/test/{id}','用于演示路径参数',1);-- 超级管理员添加权限
insert into role_resource (roleId,resourceId) value(1,1001);
insert into role_resource (roleId,resourceId) value(1,1002);
insert into role_resource (roleId,resourceId) value(1,1003);
insert into role_resource (roleId,resourceId) value(1,1004);
-- 数据管理员添加权限
insert into role_resource (roleId,resourceId) value(2,1004);

用户1超级管理员
在这里插入图片描述

用户2数据管理员
在这里插入图片描述
在这里插入图片描述
用户1超级管理员
在这里插入图片描述

用户2数据管理员
在这里插入图片描述
在这里插入图片描述
这样按钮级别权限也轻松实现了

数据权限

前面所介绍的页面权限和操作权限都属于功能权限,我们接下来要讲的就是截然不同的数据权限。

功能权限和数据权限最大的不同就在于,前者是判断有没有某权限,后者是判断有多少权限。功能权限对资源的安全判断只有YES和NO两种结果,要么你就有这个权限要么你就没有。而资源权限所要求的是,在同一个数据请求中,根据不同的权限范围返回不同的数据集。

举一个最简单的数据权限例子就是:现在列表里本身有十条数据,其中有四条我没有权限,那么我就只能查询出六条数据。接下来我就带大家来实现这个功能!

硬编码

前面所介绍的页面权限和操作权限都属于功能权限,我们接下来要讲的就是截然不同的数据权限。
功能权限和数据权限最大的不同就在于,前者是判断有没有某权限,后者是判断有多少权限。功能权限对资源的安全判断只有YES和NO两种结果,要么你就有这个权限要么你就没有。而资源权限所要求的是,在同一个数据请求中,根据不同的权限范围返回不同的数据集。
举一个最简单的数据权限例子就是:现在列表里本身有十条数据,其中有四条我没有权限,那么我就只能查询出六条数据。接下来我就带大家来实现这个功能!
公司表

create table company(id int primary key AUTO_INCREMENT comment '唯一表示',name varchar(255) comment '公司名称'
);insert into company (name) value('总公司');
insert into company (name) value('北京分公司');
insert into company (name) value('上海分公司');
insert into company (name) value('广州分公司');
insert into company (name) value('深圳分公司');

数据表

create table data(id int primary key AUTO_INCREMENT comment '唯一表示',customerName varchar(255) comment '客户姓名',customerPhone varchar(255) comment '客户手机号',companyId int comment '所属公司'
);insert into data (customerName, customerPhone, companyId)  value ('张三','12345678901',1);
insert into data (customerName, customerPhone, companyId)  value ('张三','12345678901',1);
insert into data (customerName, customerPhone, companyId)  value ('张三','12345678901',1);insert into data (customerName, customerPhone, companyId)  value ('李四','12345678902',2);
insert into data (customerName, customerPhone, companyId)  value ('李四','12345678902',2);
insert into data (customerName, customerPhone, companyId)  value ('李四','12345678902',2);
insert into data (customerName, customerPhone, companyId)  value ('李四','12345678902',2);insert into data (customerName, customerPhone, companyId)  value ('王五','12345678903',3);
insert into data (customerName, customerPhone, companyId)  value ('王五','12345678903',3);
insert into data (customerName, customerPhone, companyId)  value ('王五','12345678903',3);
insert into data (customerName, customerPhone, companyId)  value ('王五','12345678903',3);insert into data (customerName, customerPhone, companyId)  value ('赵六','12345678904',4);
insert into data (customerName, customerPhone, companyId)  value ('赵六','12345678904',4);
insert into data (customerName, customerPhone, companyId)  value ('赵六','12345678904',4);
insert into data (customerName, customerPhone, companyId)  value ('赵六','12345678904',4);insert into data (customerName, customerPhone, companyId)  value ('孙七','12345678905',5);
insert into data (customerName, customerPhone, companyId)  value ('孙七','12345678905',5);
insert into data (customerName, customerPhone, companyId)  value ('孙七','12345678905',5);
insert into data (customerName, customerPhone, companyId)  value ('孙七','12345678905',5);

在这里插入图片描述
我们权限划分也很简单,就和之前一样的,建一个中间表即可。这里为了演示,就直接将用户和公司直接挂钩了,建一个user_company表来表示用户拥有哪些公司数据权限

create table user_company(userId int comment '用户id',companyId int comment '公司id'
);
-- 超级管理员
insert into user_company(userId, companyId) value (1,1);
insert into user_company(userId, companyId) value (1,2);
insert into user_company(userId, companyId) value (1,3);
insert into user_company(userId, companyId) value (1,4);
insert into user_company(userId, companyId) value (1,5);
-- 数据管理员
insert into user_company(userId, companyId) value (2,5);

使用如下sql就可以实现,代码就不写了和之前的处理逻辑是一样的

select t1.* from data t1
left join user_company t2 on t1.companyId=t2.companyId
where t2.userId=(select t3.id from user t3 where t3.UserName='admin' limit 1);select t1.* from data t1
left join user_company t2 on t1.companyId=t2.companyId
where t2.userId=(select t3.id from user t3 where t3.UserName='wjl' limit 1);

Mybatis拦截插件

@lombok.Data
public class Data {private String customerName;private String customerPhone;private Integer companyId;
}

接口

package com.wujialiang.auth.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wujialiang.auth.entity.Data;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
@Mapper
public interface DataMapper extends BaseMapper<Data> {/*** 获取全部的数据* @return*/List<Data> getAllDatas();
}

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wujialiang.auth.mapper.DataMapper"><select id="getAllDatas" resultType="com.wujialiang.auth.entity.Data">select * from data</select>
</mapper>

修改service

package com.wujialiang.auth.service;import com.wujialiang.auth.context.UserContext;
import com.wujialiang.auth.entity.Data;
import com.wujialiang.auth.entity.Resource;
import com.wujialiang.auth.mapper.DataMapper;
import com.wujialiang.auth.mapper.ResouceMapper;
import com.wujialiang.auth.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate ResouceMapper resouceMapper;@Autowiredprivate DataMapper dataMapper;public Boolean userLogin(String userName,String password){boolean isLogin = userMapper.userLogin(userName, password);return isLogin;}public void doSomething() {String currentUserName = UserContext.getCurrentUserName();System.out.println("Service层---当前用户登录名:" + currentUserName);}/*** 获取当前用户的菜单* @return*/public List<Resource> getCurrentUserMenus(){String currentUserName = UserContext.getCurrentUserName();return resouceMapper.getCurrentUserMenus(currentUserName);}/*** 获取全部的菜单* @return*/public List<Resource> getAllMenus(){return resouceMapper.getAllMenus();}/*** 获取全部的数据* @return*/public List<Data> getAllDatas(){return dataMapper.getAllDatas();}
}

修改controller

package com.wujialiang.auth.controller;import com.wujialiang.auth.entity.Data;
import com.wujialiang.auth.entity.Resource;
import com.wujialiang.auth.entity.User;
import com.wujialiang.auth.service.UserService;
import com.wujialiang.auth.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.stream.Collectors;@RestController
public class UserController {@Autowiredprivate UserService userService;/*** 登录接口* @param user* @return*/@PostMapping("/login")public String login(@RequestBody User user) {boolean isLogin = userService.userLogin(user.getUsername(), user.getPassword());if (isLogin) {// 如果正确的话就返回生成的token(注意哦,这里服务端是没有存储任何东西的)return JwtUtil.generate(user.getUsername());}return "账号密码错误";}@GetMapping("/jwttest")public String api() {userService.doSomething();return "api成功返回数据";}/*** 获取当前用户的菜单* @return*/@GetMapping("/menus")public List<Resource> getMenus() {return userService.getCurrentUserMenus();}/*** 删除用户测试接口* @return*/@DeleteMapping("/api/user")public String deleteUser() {// 拿到所有权限路径 和 当前用户拥有的权限路径List<Resource> allMenuss = userService.getAllMenus();List<Resource> userMenus = userService.getCurrentUserMenus();List<String> userPaths = userMenus.stream().map(Resource::getPath).collect(Collectors.toList());List<String> allPaths = allMenuss.stream().map(Resource::getPath).collect(Collectors.toList());// 第一个判断:所有权限路径中包含该接口,才代表该接口需要权限处理,所以这是先决条件,如果权限路径中没有则放行// 第二个判断:判断该接口是不是属于当前用户的权限范围,如果不是,则代表该接口用户没有权限if (allPaths.contains("DELETE:/api/user") && !userPaths.contains("DELETE:/api/user")) {return "您没权限操作";}return "操作成功";}/*** 获取当前用户的菜单* @return*/@GetMapping("/test/data")public List<Data> getDatas() {return userService.getAllDatas();}
}

拦截器方法

package com.wujialiang.auth.interceptor;import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;/*** 这里是专门针对Mybatis-plus3.4.0分页拦截器做的SQL拦截插件** @author RudeCrab*/
@Slf4j
public class MyPaginationInterceptor implements InnerInterceptor {@Overridepublic void beforePrepare(StatementHandler statementHandler, Connection connection, Integer transactionTimeout) {MetaObject metaObject = SystemMetaObject.forObject(statementHandler);MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");// id为执行的mapper方法的全路径名,如com.rudecrab.mapper.UserMapper.insertUserString id = mappedStatement.getId();log.info("mapper: ==> {}", id);// 如果不是指定的方法,直接结束拦截// 如果方法多可以存到一个集合里,然后判断当前拦截的是否存在集合中if (!id.startsWith("com.wujialiang.auth.mapper.DataMapper.getAllDatas")) {return;}// 获取到原始sql语句String sql = statementHandler.getBoundSql().getSql();log.info("原始SQL语句: ==> {}", sql);sql = getSql(sql);// 修改sqlmetaObject.setValue("delegate.boundSql.sql", sql);log.info("拦截后SQL语句:==>{}", sql);}/*** 解析SQL语句,并返回新的SQL语句** @param sql 原SQL* @return 新SQL*/private String getSql(String sql) {try {// 解析语句Statement stmt = CCJSqlParserUtil.parse(sql);Select selectStatement = (Select) stmt;PlainSelect ps = (PlainSelect) selectStatement.getSelectBody();// 拿到表信息FromItem fromItem = ps.getFromItem();Table table = (Table) fromItem;String mainTable = table.getAlias() == null ? table.getName() : table.getAlias().getName();List<Join> joins = ps.getJoins();if (joins == null) {joins = new ArrayList<>(1);}// 创建连表join条件Join join = new Join();join.setInner(true);join.setRightItem(new Table("user_company uc"));// 第一个:两表通过company_id连接EqualsTo joinExpression = new EqualsTo();joinExpression.setLeftExpression(new Column(mainTable + ".companyId"));joinExpression.setRightExpression(new Column("uc.companyId"));// 第二个条件:和当前登录用户id匹配EqualsTo userIdExpression = new EqualsTo();userIdExpression.setLeftExpression(new Column("uc.userId"));userIdExpression.setRightExpression(new LongValue("1"));// 将两个条件拼接起来join.setOnExpression(new AndExpression(joinExpression, userIdExpression));joins.add(join);ps.setJoins(joins);// 修改原语句sql = ps.toString();} catch (JSQLParserException e) {e.printStackTrace();}return sql;}
}

新建config

package com.wujialiang.auth.config;import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.wujialiang.auth.interceptor.MyPaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author Mybatisplus配置*/
@Configuration
public class MybatisPlusConfig {/*** 新的分页插件,一缓和二缓遵循mybatis的规则,* 需要设置 MybatisConfiguration#useDeprecatedExecutor = false* 避免缓存出现问题(该属性会在旧插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new MyPaginationInterceptor());return interceptor;}@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> configuration.setUseDeprecatedExecutor(false);}
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
至此完成行数据修改
在这里插入图片描述

在这里插入图片描述

参考

https://www.cnblogs.com/RudeCrab/p/14251274.html
https://www.cnblogs.com/RudeCrab/p/14251154.html
https://blog.csdn.net/qq1910506668/article/details/136608184

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/830085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

秋招后端开发面试题 - JVM底层原理

目录 JVM底层原理前言面试题Java 对象的创建过程&#xff1f;什么是指针碰撞&#xff1f;什么是空闲列表&#xff1f;/ 内存分配的两种方式&#xff1f;JVM 里 new 对象时&#xff0c;堆会发生抢占吗&#xff1f;JVM 是怎么设计来保证线程安全的&#xff1f;/ 内存分配并发问题…

k8s pod使用sriov

之前的文章中讲了k8s multus的使用&#xff0c;本章节来讲述下如何使用multus来实现sriov的使用。 一、sriov 简介 SR-IOV在2010年左右由Intel提出&#xff0c;但是随着容器技术的推广&#xff0c;intel官方也给出了SR-IOV技术在容器中使用的开源组件&#xff0c;例如&#…

3MF体积设计扩展

3MF 联盟最近宣布了他们最新的体积设计扩展&#xff08;volumetric design extension&#xff09;&#xff0c;用于通过基于体积的描述来编码几何形状和空间多样性属性。 该组织致力于推进 3D 打印的通用规范&#xff0c;目前正在新扩展达到 1.0 之前征求公众反馈。 NSDT工具推…

OpenCV 实现重新映射

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV 实现霍夫圆变换 下一篇 :OpenCV实现仿射变换 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 一个。使用 OpenCV 函数 cv&#xff1a;&#xff1a;remap 实现简单的重新…

20240428如何利用IDM下载磁链视频

缘起&#xff1a; https://weibo.com/tv/show/1034:4864336909500449 中国获奖独立纪录片《阿辉》揭秘红灯区“教父”的生存法则 5,751次观看 1年前 发布于 陕西 身为里中横 67.7万粉丝 互联网科技博主 微博原创视频博主 头条文章作者 https://weibo.com/tv/show/1034:4864…

数据通信-A

数据通信 一、数据通信网络基础二、VRP系统三、eNSP配置命令 不是从零开始&#xff0c;有一些基础&#xff0c;主要记录配置命令。一、数据通信网络基础 图标&#xff1a;主要是认识第一行。 常见术语&#xff1a;数据通信网络最基本的功能是实现数据互通。 数据载荷&#…

解决IDEA下springboot项目打包没有主清单属性

1.问题出现在SpringBoot学习中 , 运行maven打包后无法运行 报错为spring_boot01_Demo-0.0.1-SNAPSHOT.jar中没有主清单属性 SpringBoot版本为 2.6.13 Java 版本用的8 解决方法 1.执行clean 删除之前的打包 2.进行打包规范设置 2.1 3.进行问题解决 (借鉴了阿里开发社区) 使用…

[嵌入式系统-53]:嵌入式系统集成开发环境大全

目录 一、嵌入式系统集成开发环境分类 二、由MCU芯片厂家提供的集成开发工具 三、由嵌入式操作提供的集成开发工具 四、由第三方工具厂家提供的集成开发工具 一、嵌入式系统集成开发环境分类 嵌入式系统集成开发工具和集成开发环境可以按照不同的分类方式进行划分&#xff…

【LAMMPS学习】八、基础知识(5.2)粒度模型

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

将针孔模型相机 应用到3DGS

Motivation 3DGS 的 投影采用的是 CG系的投影矩阵 P P P, 默认相机的 principal point (相机光心) 位于图像的中点处。但是 实际应用的 绝大多数的 相机 并不满足这样一个设定&#xff0c; 因此我们 需要根据 f , c x , c y {f,c_x, c_y} f,cx​,cy​ 这几个参数重新构建3D …

centos 7 yum install -y nagios

centos 7 systemctl disable firewalld --now vi /etc/selinux/config SELINUXdisabled yum install -y epel-release httpd nagios yum install -y httpd nagios systemctl enable httpd --now systemctl enable nagios --now 浏览器 IP/nagios 用户名&#xff1a;…

vue学习的预备知识为学好vue打好基础

目录 Vue是什么 &#xff1f;如何使用Vue &#xff1f;Vue ApiVue入口apiVue实例apiVue函数api 无构建过程的渐进式增强静态HTMLVue模块化构建工具npmyarnWebpackvue-cliVite Vue是什么 &#xff1f; 文章基于Vue3叙述。 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于…

十大USDT交易平台大全XEX交易所

USDT是一种基于比特币区块链网络的加密代币&#xff0c;主要运用于数字货币交易平台&#xff0c;以稳定币为主。USDT的核心价值在于其与真实货币的固定兑换比率1:1&#xff0c;所以被称为Tether。随着加密货币市场的不断壮大&#xff0c;越来越多的交易平台开始支持USDT&#x…

2024深圳杯(东北三省)数学建模C题完整论文讲解(含完整python代码及所有残骸音爆位置求解结果)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024深圳杯&#xff08;东北三省数学建模联赛&#xff09;A题多个火箭残骸的准确定位完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊…

【vscode环境配置系列】vscode远程debug配置

VSCODE debug环境配置 插件安装配置文件debug 插件安装 安装C/C, C/C Runner 配置文件 在项目下建立.vscode文件夹&#xff0c;然后分别建立c_cpp_properties.json&#xff0c; launch.json&#xff0c;tasks.json&#xff0c;内容如下&#xff1a; c_cpp_properties.json:…

如何解决pycharm创建项目报错 Error occurred when installing package ‘requests‘. Details.

&#x1f42f; 如何解决PyCharm创建项目时的包安装错误&#xff1a;‘requests’ &#x1f6e0;️ 文章目录 &#x1f42f; 如何解决PyCharm创建项目时的包安装错误&#xff1a;requests &#x1f6e0;️摘要引言正文&#x1f4d8; **问题分析**&#x1f680; **更换Python版本…

如何利用快解析软件搭建映射端口

端口映射&#xff0c;就是将内网中主机的一个端口映射到外网主机的一个端口&#xff0c;提供相应的服务&#xff0c;当用户访问外网IP的这个端口时&#xff0c;服务器自动将请求映射到对应局域网内部的机器上。如何才能实现端口映射&#xff1f;今天小编给大家介绍两种方法&…

fetch请求后端返回文件流,并下载。

前端&#xff1a; <script src"~/layui/layui.js"></script> <script src"~/Content/js/common/js/vue.min.js"></script> <script src"~/Content/js/common/js/jquery-1.10.2.min.js"></script><styl…

QT学习篇—qt软件安装

qt下载网址http://download.qt.io/new_archive/qt/ QT官网Qt | Tools for Each Stage of Software Development LifecycleAll the essential Qt tools for all stages of Software Development Lifecycle: planning, design, development, testing, and deployment.https:…

虚拟机扩容-根目录挂载sda1的空间不足

提醒&#xff01;不管成不成功&#xff0c;一定要先备份一份虚拟机&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 走过路过点个关注吧&#xff0c;想到500粉丝&#xff0c;哭。一、查看分区情况 df -h可以看到/dev/sda1已经被占满了 2.关闭虚拟机&#xff…