mybatisplus数据权限插件学习初探 动态表名更换插件 防止全表更新与删除插件

news2024/12/28 18:38:45

文章目录

    • 学习链接
  • mybatisplus数据权限插件学习初探
    • 前言
    • 案例
      • 建表
        • 用户表
        • 订单表
      • 环境准备
        • User
        • UserMapper
        • UserMapper.xml
        • Orders
        • OrdersMapper
        • OrdersMapper.xml
      • 配置
        • UserTypeEnum
        • UserContextHolder
        • CustomizeDataPermissionHandler
        • MybatisPlusConfig
      • 测试
        • 测试类
        • boss
        • deptManager
        • clerk
  • 动态表名更换插件
    • 案例
      • 代码
        • MyBatisplusConfig
        • AccountController
        • AccountMapper
        • AccountMapper.xml
      • 测试
  • 防止全表更新与删除插件
    • 案例
      • 代码
        • 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

动态表名更换插件

这个插件比较简单,就是在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
在这里插入图片描述

防止全表更新与删除插件

这个插件可以防止全表更新与删除插件,就是如果不带where条件DML的sql会抛出异常,但是有的时候,就是需要全表删除呢?可以在mapper方法上使用@InterceptorIgnore(blockAttack = “true”)注解标注此方法即可。

案例

代码

MyBatisplusConfig

直接加就完了

@Slf4j
@Configuration
public class MyBatisplusConfig {

    @Bean
    public MybatisPlusInterceptor interceptor() {
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }

}

AccountController

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

    @GetMapping("deleteAll")
    public Object deleteAll() {
        accountMapper.deleteAll();
        return "删除成功";
    }

    @GetMapping("updateAll")
    public Object updateAll() {
        LambdaUpdateWrapper<Account> updateWrapper = new LambdaUpdateWrapper<Account>().set(Account::getNickName, "005");
        accountMapper.update(null, updateWrapper);
        return "修改成功";
    }

}

AccountMapper

public interface AccountMapper extends BaseMapper<Account> {

    @InterceptorIgnore(blockAttack = "true") // true表示不启用插件
    void deleteAll();
}

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">

    <delete id="deleteAll">
        delete from account
    </delete>

</mapper>

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

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

相关文章

Redis通信协议、过期回收策略

Redis通信协议-RESP协议 Redis是一个CS架构的软件&#xff0c;通信一般分两步&#xff08;不包括pipeline和PubSub&#xff09;&#xff1a; 客户端&#xff08;client&#xff09;向服务端&#xff08;server&#xff09;发送一条命令 服务端解析并执行命令&#xff0c;返回…

二级指针骚操作实现链表虚拟头节点

重点是不用像其他文章里那样&#xff0c;用一个普通节点成员变量当头节点&#xff0c;节省一点空间占用&#xff0c;反正我觉得有点骚。就不详细交代技术背景了&#xff0c;简而言之&#xff0c;就是链表中第一个节点前没有节点了&#xff0c;只有一个指向它的指针&#xff0c;…

强化学习基础篇[3]:DQN、Actor-Critic详解

【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍:【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应…

从实习到秋招成为一名安全工程师,我是怎么做的

前言 借朋友口述总结了安全招聘面试经历分享&#xff0c;希望更多的人看到这篇文&#xff0c;从中得到启发&#xff0c;找到自己心仪的工作。 基本情况 签了字节的三方&#xff0c;秋招终于告一段落。从八月份边实习边准备秋招到现在&#xff0c;经历了许多&#xff0c;这篇帖…

Linux :: 【简单开发篇 :: vim 编辑器:(1)】:: vim 编辑器的基本认识与三种 vim 常用模式 | 使用:打开编辑、退出保存关闭vim

前言&#xff1a;本篇是 Linux 基本操作篇章的内容&#xff01; 笔者使用的环境是基于腾讯云服务器&#xff1a;CentOS 7.6 64bit。 学习集&#xff1a; C 入门到入土&#xff01;&#xff01;&#xff01;学习合集Linux 从命令到网络再到内核&#xff01;学习合集 目录索引&am…

yolov8Pose实战

目录 前言一、yolov8环境搭建二、测试训练模型&#xff0c;评估模型&#xff0c;并导出模型实测检测效果 测试人体姿态估计 前言 YOLO系列层出不穷&#xff0c;从yolov5到现在的yolov8仅仅不到一年的时间。追踪新技术&#xff0c;了解前沿算法&#xff0c;一起来测试下yolov8在…

【密码学复习】第十章 身份鉴别

身份鉴别的定义 定义&#xff1a;身份鉴别&#xff0c;又称为身份识别、身份认证。它是证实客户的真实身份与其所声称的身份是否相符的过程。 口令身份鉴别 固定口令&#xff08;四&#xff09; 注册环节&#xff1a;双因子认证 ① 接收用户提供的口令pw&#xff08;PIN&…

车辆救援道路救援预约汽修托运小程序

道路救援&#xff1a;指汽车道路紧急救援&#xff0c;为故障车主提供包括诸如&#xff1a;拖吊、换水、充电、换胎、送油以及现场小修等服务(Road-Side Service)&#xff1b; 同时也指交通事故道路救援&#xff0c;包括伤员救治、道路疏导等。 随着我国巨大的汽车拥有量&…

1计算机系统概述_1.2计算机系统层次结构

1.2 计算机系统层次结构 计算机系统&#xff08;CO 自命名&#xff09; 1、CO的组成 硬件系统和软件系统共同构成了一个完整的计算机系统 ——硬件&#xff1a;有形的物理设备&#xff0c;是CO中实际物理装置的总称 ——软件&#xff1a;在硬件上运行的程序和相关的数据及文…

SpringCloud:分布式缓存之Redis哨兵

Redis提供了哨兵&#xff08;Sentinel&#xff09;机制来实现主从集群的自动故障恢复。 1.哨兵原理 1.1.集群结构和作用 哨兵的结构如图&#xff1a; 哨兵的作用如下&#xff1a; 监控&#xff1a;Sentinel会不断检查您的master和slave是否按预期工作自动故障恢复&#xff…

人工智能(pytorch)搭建模型9-pytorch搭建一个ELMo模型,实现训练过程

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型9-pytorch搭建一个ELMo模型&#xff0c;实现训练过程&#xff0c;本文将介绍如何使用PyTorch搭建ELMo模型&#xff0c;包括ELMo模型的原理、数据样例、模型训练、损失值和准确率的打印以及…

labelimg闪退解决方法(之前使用过labelimg,但新一次使用,打开文件夹无反应,再次打开闪退的问题)及标注经验

问题描述&#xff1a; 之前使用过labelimg进行好多次的标注&#xff0c;但新一次运行使用&#xff0c;发现打开目录无反应&#xff0c;再次打开闪退的问题&#xff0c;重启电脑并且从新运行labelimg仍然无效。 解决方法&#xff1a; 关闭labelimg&#xff0c;然后删除文件C…

一文纵览Umi‘s Friends生态,GameFi浪潮的变革者

以“P2E”为特性的 GameFi&#xff0c;代表着游戏时代的新盈利模式&#xff0c;它将 NFT 或其他形式的代币化资产作为游戏内容&#xff0c;游戏内资产的寿命会&#xff0c;则随着这些资产继续存在于玩家的钱包中而延长&#xff08;即便游戏关闭&#xff09;&#xff0c;资产的互…

class文件中,常量池、方法表、属性表,异常表等等相关数据解析!小白就跟我一起对照学【class字节码文件分析】

前言&#xff1a;前段时间读《深入java虚拟机》介绍到class文件的时候&#xff0c;由于理论知识较多&#xff0c;人总感觉疲惫不堪&#xff0c;就泛泛阅读了一下。在工作中使用起来知识点知道&#xff0c;但是总是需要查阅各种资料。今天有时间&#xff0c;继续整理常量池后面的…

session与cookie

session是一种会话机制。当客户端发送登录请求时&#xff0c;服务端会生成一个sessionId存储在cookie中返回给客户端&#xff0c;客户端通过响应数据中的set-cookie字段来获取cookie并保存。如果客户端再向同一网站发送请求时&#xff0c;会自动携带cookie&#xff0c;相当于一…

离散数学_十章-图 ( 5 ):连通性 - 下

&#x1f4f7;10.5 图的连通性 4. 有向图的连通性4.1 强连通4.2 弱连通4.3 &#xff08;有向图的&#xff09;强连通分支 5. 通路与同构6. 顶点间通路个数的计算 4. 有向图的连通性 根据是否考虑边的方向&#xff0c;在有向图中有两种连通性概念&#xff1a; 4.1 强连通 强连…

C/C++线程绑核详解

在一些大型的工程或者特殊场景中&#xff0c;我们会听到绑核&#xff0c;绑核分为进程绑核和线程绑核。绑核的最终目的都是为了提高程序和性能或者可靠性。 一&#xff1a;为什么需要绑核 操作系统发展至今&#xff0c;已经能很好的平衡运行在操作系统上层的应用&#xff0c;兼…

16.3:岛屿数量问题2

岛屿数量问题2 https://leetcode.cn/problems/number-of-islands-ii/ 给你一个大小为 m x n 的二进制网格 grid 。网格表示一个地图&#xff0c;其中&#xff0c;0 表示水&#xff0c;1 表示陆地。最初&#xff0c;grid 中的所有单元格都是水单元格&#xff08;即&#xff0c…

Dubbo源码解析一网络通信原理

Dubbo 网络通信原理 1. Dubbo高可用集群1.1 服务集群的概述1.1.1 服务集群的概述1.1.2 调用过程1.1.3 组件介绍 1.2 集群容错机制1.2.1 内置集群容错策略1.2.1.1 Failover(失败自动切换)1.2.1.2 Failsafe(失败安全)1.2.1.3 Failfast(快速失败)1.2.1.4 Failback(失败自动恢复)1.…

卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering) 研究内容