mybatisplus数据权限插件学习初探 动态表名更换插件

news2024/11/24 14:05:17

文章目录

    • 学习链接
  • mybatisplus数据权限插件学习初探
    • 前言
    • 案例
      • 建表
        • 用户表
        • 订单表
      • 环境准备
        • User
        • UserMapper
        • UserMapper.xml
        • Orders
        • OrdersMapper
        • OrdersMapper.xml
      • 配置
        • UserTypeEnum
        • UserContextHolder
        • CustomizeDataPermissionHandler
        • MybatisPlusConfig
      • 测试
        • 测试类
        • boss
        • deptManager
        • clerk
  • mybatisplus动态表名更换插件
    • 动态表名更换插件
    • 案例
      • 代码
        • MyBatisplusConfig
        • AccountController
        • AccountMapper
        • AccountMapper.xml
      • 测试

学习链接

Mybatis-Plus入门系列(3)- MybatisPlus之数据权限插件DataPermissionInterceptor

jsqlparser学习 - 自己收藏的链接

Mysql递归查询子级(父子级结构)&从子级ID查询所有父级(及扩展知识) - 自己的链接
Mysql带层级(父子级)的递归查询案例 - 自己的链接

mybatisplus数据权限插件学习初探

前言

对于系统中的不同用户,对于同一接口,可能都有权限访问此接口,但是由于用户各自的权限大小,看到的数据不一样(数据权限)。

就比如:有一张用户表,每个用户只能属于某一个部门,每个用户都有自己的用户类型(用户类型有老板、部门经理、普通职工),每个员工的订单记录在订单表中,订单属于创建这个订单的用户,订单也属于这个用户的部门。

  • 对于老板来说,能看到所有的订单
  • 对于部门经理来说,能够看到自己部门(可能还有子部门,这里暂不考虑)的订单
  • 对于普通职工来说,只能看到自己的订单

如果对于订单表的查询有多个地方,或者又不仅仅是订单表需要按上面的规则来作数据权限控制,那么在service层的每个地方几乎都要来上面的用户类型判断,然后再写不同的sql来做对应的查询。

所以如果能在dao层能够根据当前用户类型,自动的拼接对应的条件,那样是比较方便的,下面演示的案例仅作为自己入门学习案例记录,对于复杂的sql,需要深入学习下Jsqlparser,参考一些开源项目的做法。

值得思考的问题:

  • 如何进行数据权限的划分?按部门划分是一种方案,但是部门之间有可能会存在上下级,有些人可能属于多个部门(同时存在上下级部门)。有没有其它的划分方式?
  • 复杂sql的处理?

案例

建表

在这里插入图片描述

用户表

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `type` int(1) DEFAULT NULL COMMENT '用户类型',
  `tenant_id` bigint(20) NOT NULL COMMENT '租户ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `dept_id` int(11) DEFAULT NULL COMMENT '所属部门id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `test`.`user` (`id`, `type`, `tenant_id`, `name`, `dept_id`) VALUES (1, 0, 1, 'mp', NULL);
INSERT INTO `test`.`user` (`id`, `type`, `tenant_id`, `name`, `dept_id`) VALUES (2, 1, 1, 'Jack', 1);
INSERT INTO `test`.`user` (`id`, `type`, `tenant_id`, `name`, `dept_id`) VALUES (3, 1, 1, 'Sandy', 2);
INSERT INTO `test`.`user` (`id`, `type`, `tenant_id`, `name`, `dept_id`) VALUES (4, 2, 1, 'Billie', 1);
INSERT INTO `test`.`user` (`id`, `type`, `tenant_id`, `name`, `dept_id`) VALUES (5, 2, 1, 'Sally', 1);
INSERT INTO `test`.`user` (`id`, `type`, `tenant_id`, `name`, `dept_id`) VALUES (6, 2, 1, 'Kevin', 2);

订单表

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orders_no` varchar(10) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `dept_id` int(11) DEFAULT NULL,
  `tenant_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `test`.`orders` (`id`, `orders_no`, `user_id`, `dept_id`, `tenant_id`) VALUES (1, '001', 4, 1, 1);
INSERT INTO `test`.`orders` (`id`, `orders_no`, `user_id`, `dept_id`, `tenant_id`) VALUES (2, '002', 5, 1, 1);
INSERT INTO `test`.`orders` (`id`, `orders_no`, `user_id`, `dept_id`, `tenant_id`) VALUES (3, '003', 6, 2, 1);

环境准备

User

@Data
@Accessors(chain = true)
public class User {

    /**
     * 租户 ID
     */
    private Long tenantId;

    @TableId(type = IdType.AUTO)
    private Long id;

    private Integer type;

    private Integer deptId;

    private String name;

}

UserMapper

public interface UserMapper extends BaseMapper<User> {
}

UserMapper.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.baomidou.mybatisplus.samples.dataPerm.mapper.UserMapper">


</mapper>

Orders

@Data
@Accessors(chain = true)
public class Orders {

    private String id;

    private String ordersNo;

    private Integer userId;

    private String deptId;

}

OrdersMapper

public interface OrdersMapper extends BaseMapper<Orders> {

    List<Orders> selectOrdersList();

}

OrdersMapper.xml

这里起初并没有where条件

<?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.baomidou.mybatisplus.samples.dataPerm.mapper.OrdersMapper">

    <select id="selectOrdersList" resultType="com.baomidou.mybatisplus.samples.dataPerm.entity.Orders">
        SELECT * FROM orders
    </select>

</mapper>

配置

UserTypeEnum

@Getter
public enum UserTypeEnum {
    BOSS(0,"老板"),
    DEPT_MANAGER(1,"部门经理"),
    CLERK(1,"普通职员"),
    DEFAULT(1,"普通职员"),
    ;
    Integer type;
    String desc;

    UserTypeEnum(Integer type, String desc) {
        this.type = type;
        this.desc = desc;
    }

    public static UserTypeEnum type(Integer type) {
        for (UserTypeEnum value : UserTypeEnum.values()) {
            if (Objects.equals(type, value.type)) {
                return value;
            }
        }
        return DEFAULT;
    }

}

UserContextHolder

public class UserContextHolder {

    private static final ThreadLocal<User> USER_THREAD_LOCAL = new ThreadLocal<>();

    public static void bindUser(User user) {
        USER_THREAD_LOCAL.set(user);
    }

    public static void unBindUser() {
        USER_THREAD_LOCAL.remove();
    }

    public static User getUser() {
        return USER_THREAD_LOCAL.get();
    }

}

CustomizeDataPermissionHandler

@Component
public class CustomizeDataPermissionHandler implements DataPermissionHandler {

    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {

        User user = UserContextHolder.getUser();

        if (user == null) {
            return where;
        }

        UserTypeEnum type = UserTypeEnum.type(user.getType());
        if (UserTypeEnum.BOSS == type) {
            return where;
        } else if (UserTypeEnum.DEPT_MANAGER == type) {

            // 部门下可能存在子部门(需要查询当前部门的所有子部门(包括当前部门),可以使用sql递归), 这里暂时就认为只有1个
            StringValue deptIdStringValue = new StringValue(String.valueOf(user.getDeptId()));
            ExpressionList expressionList = new ExpressionList(deptIdStringValue);

            InExpression inExpression = new InExpression(new Column("orders.dept_id"), expressionList);

            if (where == null) {
                // 如果原来没有where条件, 就添加一个where条件
                return inExpression;
            } else {
                return new AndExpression(where, inExpression);
            }


        } else {

            EqualsTo equalsTo = new EqualsTo(new Column("orders.user_id"), new StringValue(String.valueOf(user.getUserId())));

            if (where == null) {
                // 如果原来没有where条件, 就添加一个where条件
                return equalsTo;
            } else {
                return new AndExpression(where, equalsTo);
            }

        }
    }

}

MybatisPlusConfig

@Configuration
@MapperScan("com.baomidou.mybatisplus.samples.dataPerm.mapper")
public class MybatisPlusConfig {

    @Autowired
    private CustomizeDataPermissionHandler dataPermissionHandler;

    /**
     * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        interceptor.addInnerInterceptor(new DataPermissionInterceptor(dataPermissionHandler));
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                User user = UserContextHolder.getUser();
                if (user != null) {
                    return new LongValue(user.getTenantId());
                } else {
                    return null;
                }
            }

            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            @Override
            public boolean ignoreTable(String tableName) {
                return Objects.equals("user", tableName);
            }
        }));

        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setDialect(DialectFactory.getDialect(DbType.MYSQL));
        interceptor.addInnerInterceptor(paginationInnerInterceptor);

        // interceptor.addInnerInterceptor();
        // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
        // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
//        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }

//    @Bean
//    public ConfigurationCustomizer configurationCustomizer() {
//        return configuration -> configuration.setUseDeprecatedExecutor(false);
//    }
}

测试

测试类

@Slf4j
@SpringBootTest(classes = TenantApplication.class)
public class TenantTest {

    @Resource
    private UserMapper userMapper;

    @Resource
    private OrdersMapper ordersMapper;

    @Test
    public void test001() {

        // User user = userMapper.selectById(1);
        // User user = userMapper.selectById(2);
        User user = userMapper.selectById(4);
        UserContextHolder.bindUser(user);

        List<Orders> orders = ordersMapper.selectOrdersList();
        for (Orders order : orders) {
            log.info("{}", order);
        }

        Page<Orders> ordersPage = new Page<>(1, 1);
        ordersMapper.selectPage(ordersPage, null);
        log.info("total:{},pages:{},data:{}",ordersPage.getTotal(),ordersPage.getPages(), ordersPage.getRecords());

        UserContextHolder.unBindUser();
    }

}

boss

老板可以看到所有的数据。可以看到下面,仅拼接了租户id

SELECT id, tenant_id, type, dept_id, name FROM user WHERE id = 1

SELECT * FROM orders WHERE orders.tenant_id = 1

SELECT COUNT(*) AS total FROM orders WHERE orders.tenant_id = 1
SELECT id, orders_no, user_id, dept_id FROM orders WHERE orders.tenant_id = 1 LIMIT 1

deptManager

部门经理可以看到本部门及子部门的数据。可以看到下面拼接了in的条件

SELECT id, tenant_id, type, dept_id, name FROM user WHERE id = 2

SELECT * FROM orders WHERE orders.dept_id IN ('1') AND orders.tenant_id = 1

SELECT COUNT(*) AS total FROM orders WHERE orders.dept_id IN ('1') AND orders.tenant_id = 1
SELECT id, orders_no, user_id, dept_id FROM orders WHERE orders.dept_id IN ('1') AND orders.tenant_id = 1 LIMIT 1

clerk

普通职员只能看到自己的数据。可以看到拼接的条件使用user_id

SELECT id, tenant_id, type, dept_id, name FROM user WHERE id = 4

SELECT * FROM orders WHERE orders.user_id = '4' AND orders.tenant_id = 1

SELECT COUNT(*) AS total FROM orders WHERE orders.user_id = '4' AND orders.tenant_id = 1
SELECT id, orders_no, user_id, dept_id FROM orders WHERE orders.user_id = '4' AND orders.tenant_id = 1 LIMIT 1

mybatisplus动态表名更换插件

动态表名更换插件

这个插件比较简单,就是在sql中遇到表名,会把这个表名传递给TableNameHandler处理器,然后在TableNameHandler处理器的dynamicTableName方法中获取到表名,然后返回一个新的表名替换掉sql中的原表名。

案例

代码

MyBatisplusConfig

@Slf4j
@Configuration
public class MyBatisplusConfig {

    @Bean
    public MybatisPlusInterceptor interceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
        dynamicTableNameInnerInterceptor.setTableNameHandler(new TableNameHandler() {
            @Override
            public String dynamicTableName(String sql, String tableName) {
                log.info("sql: {}", sql);
                log.info("tableName: {}", tableName);
                return tableName + "_1";
            }
        });
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        return interceptor;
    }

}

AccountController

@RestController
@RequestMapping("/account")
public class AccountController {

    @RequestMapping("getAccounts")
    public List<Account> getAccounts() {
    
    	// 调用mybatisplus的方法
        return accountService.list();
    }

    @RequestMapping("getAccounts2")
    public List<Account> getAccounts2() {
    	
    	// 调用自己写的mapper方法 
        return accountMapper.find();
    }
}

AccountMapper

public interface AccountMapper extends BaseMapper<Account> {

    List<Account> find();
}

AccountMapper.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.zzhua.mapper.AccountMapper">

    <select id="find" resultMap="BaseResultMap">
        SELECT * FROM account a INNER JOIN user u on a.user_id = u.id
    </select>

</mapper>

测试

访问:http://localhost:8080/account/getAccounts,可以看到表名替换了

在这里插入图片描述
访问:http://localhost:8080/account/getAccounts2,可以看到2个表名都会经过TableNameHandler
在这里插入图片描述

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

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

相关文章

Zinx框架学习 - 消息封装

Zinx - V0.5 消息封装 之前我们使用Request来保存服务器的数据&#xff0c;很显然使用[]byte来接收数据&#xff0c;没有长度也没有消息类型&#xff0c;接下来就要针对这个消息进行封装 创建消息类型 定义一个基本的message包&#xff0c;会包含消息ID、数据、数据长度三个…

路径规划算法:基于探路者优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

涉及float和double

文章目录 涉及float和double的问题&#xff1a;它们的存储方式&#xff1a;有效位&#xff1f; 链式结构 涉及float和double的问题&#xff1a; 它们的存储方式&#xff1a; 它们会分成小数部分和指数部分分别存储。小数部分的有效位数越多&#xff0c;精度就越高&#xff0c;…

NLP超详细新手快速入门上手篇(1)常用函数

前言 自然语言处理(NLP)是机器学习的应用之一&#xff0c;用于分析、理解和生成自然语言&#xff0c;以便人类与计算机&#xff0c;人类与人类更好的交流。自然语言处理按照任务类型可以分为分类、匹配、翻译、结构化预测、与序贯决策过程这五类。 本篇参考自TensorFlow官方文…

MyBatis 查询数据库

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 MyBatis 是什么&#xff1f;第⼀个MyBatis查询创建数据库和表添加MyBatis框架支持设置 MyBatis 配置信息添加业务代码 查询操…

【VBA】实现批量生成二维码

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

Nginx - ​一个高性能、灵活可靠的开源Web服务器

Nginx是什么&#xff1f; Nginx是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.ru站点&#xff08;俄文&#xff1a;Рамблер&#xff09;开发的&#xff0c;第一个公开版本0.1…

idea使用native-image打包springboot项目

native-image简介 native-image 是一个用于将 Java 程序编译为本地可执行文件的工具。它是 GraalVM 的一部分&#xff0c;GraalVM 是一个高性能的通用虚拟机&#xff0c;支持多种语言。 使用步骤 下载GraalVM 安装 GraalVM&#xff1a;首先&#xff0c;你需要安装 GraalVM。…

20230603-周六随笔

周六闲来无事&#xff0c;给新电脑装下开发环境&#xff0c;记录一下遇到的问题 git下载代码报错 报错1&#xff1a;schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)解决方法&#xff1a;执行git config --system http.sslbackend openssl命令 报错2&#xff1a;SSL certifica…

【Java 8 新特性】获取对象列表中的某个属性组成的列表

文章目录 获取对象列表中的某个属性组成的列表1、用法示例2、详细案例 附录&#xff1a;Java 8 Stream 基本用法1、map2、filter3、forEach4、limit5、sorted6、并行&#xff08;parallel&#xff09;程序7、Collectors8、统计 获取对象列表中的某个属性组成的列表 1、用法示例…

高完整性系统工程(十一):Fault Tolerant Design

目录 1. INTRODUCTION TO FAULT TOLERANCE 1.2 Definitions 1.3 Two Kinds of Faults 1.4 Hardware vs Software Faults 1.4.1 Failure Curve for Hardware 1.4.2 Hardware and Software Failures 1.5 Causes of Failures 1.6 3 Ways to Class Failures 1.6.1 Tempora…

【LLM】大模型值得探索的十个研究方向

note 基础理论&#xff1a;大模型的基础理论是什么&#xff1f; 网络架构&#xff1a;Transformer是终极框架吗&#xff1f; 高效计算&#xff1a;如何使大模型更加高效&#xff1f; 高效适配&#xff1a;大模型如何适配到下游任务&#xff1f; 可控生成&#xff1a;如何实…

ChatGPT有关的模块知多少?

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 ChatGPT有关的模块知多少&#xff1f; &#x1f9ca;摘要&#x1f9ca;ChatGPT 开发库清单 &#x1f9ca;摘要 本文介绍了基于OpenAI ChatGPT 的API 开发的python 模块库。【原创&am…

STM32cubemx定时外部模式测量10M以上频率

STM32cubemx定时外部模式测量10M以上频率 本文讲解利用定时器的外部时钟功能&#xff0c;巧妙测量高频外部信号频率。范围可以到高达30M以上。 所需工具&#xff1a; 开发板:STM32F103RCT6STM32CubeMXIDE: Keil-MDK 文章目录 STM32cubemx定时外部模式测量10M以上频率原理讲解…

【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(Optional篇)

Guava相关的介绍 Guava工程包含了许多被Google的Java项目广泛依赖的核心库。我们希望通过本文档为Guava中最流行和最强大的功能提供更具可读性和解释性的说明。 本教程是中级教程&#xff0c;适合 Guava 中级开发者的进阶学习。 学习Guava前的准备工作 学习目标和计划&#xf…

Python学习笔记 - 探索集合Set

尊敬的各位&#xff0c;我是Mr.数据杨&#xff0c;非常高兴和你们一起走进Python的世界。今天&#xff0c;让我们一起探讨Python中神奇的“集合”。你们是否读过《三国演义》&#xff1f;你们应该记得那场赫赫有名的“桃园三结义”吧&#xff1f;那让我们以此为例&#xff0c;来…

Windows动态链接库的生成和使用

工程需要&#xff0c;最近在编一组Windows上的动态链接库给Python调用。之前做过Linux下C动态库的编译&#xff0c;并提供给Python调用&#xff0c;Windows下的编译跟Linux还是有些差距&#xff0c;因此花了一点时间跑通&#xff0c;在这里记录一下。 为了完整对比&#xff0c…

Hive on Spark环境搭建

Hive 引擎简介 Hive 引擎包括&#xff1a;默认 MR、tez、spark 最底层的引擎就是MR &#xff08;Mapreduce&#xff09;无需配置&#xff0c;Hive运行自带 Hive on Spark&#xff1a;Hive 既作为存储元数据又负责 SQL 的解析优化&#xff0c;语法是 HQL 语法&#xff0c;执行…

(2.54mm)TSM-120-04-S-DV-P-TR方形接线柱针脚、ADRF5545ABCPZN(通用)射频前端 SPDT

TSM-120-04-S-DV-P-TR (2.54mm) 表面安装.025"方形接线柱针脚是板对板连接器&#xff0c;有单排、双排或三排方形接线柱端子可供选择&#xff0c;带直通、直角或混合技术引脚。这些高度可靠的坚固针脚有垂直和水平两种方向&#xff0c;在混合气流 (MFG) 环境中可使用10年。…

【C语言实现简易ATM】上个C语言程序设计课,我成产品经理了?

博主简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的人。 博主主页&#xff1a;陈童学哦 所属专栏&#xff1a;C语言程序设计实验项目 如果本文对你有所帮助的话&#xff0c;还希望可以点赞&#x1f44d;收藏&#x1f4c2;支持一下…