Mybatis-Plus 租户使用

news2024/11/16 0:28:53

Mybatis-Plus 租户使用

文章目录

  • Mybatis-Plus 租户使用
  • 一. 前言
    • 1.1 租户存在的意义
    • 1.2 租户框架
  • 二. Mybatis-plus 租户
    • 2.1 租户处理器
    • 2.2 前置准备
      • 1. 依赖
      • 2. 表及数据准备
      • 3. 代码生成器
    • 2.3 使用
  • 三. 深入使用
    • 3.1 前言
    • 3.2 租户主体设值,取值
    • 3.3 部分表全量db操作
    • 3.4 全量db操作
    • 3.5 自由控制全量/租户对db操作
    • 3.6 feign传递租户
  • 四. 源码地址

一. 前言

1.1 租户存在的意义

多租户数据隔离与管理是在一个共享的软件应用程序中托管多个租户(客户)的数据,并确保每个租户的数据完全隔离、安全和可管理。需要支持相同数据(记录)可由具有不同容量的多个租户。

1.2 租户框架

​ mybatis-plus租户插件使用

由于业务数据,都需要通过对数据库的crud,而此时mybatis-plus作为orm框架,提供了租户功能,我们可以合理利用拓展,应用于自己的业务范畴中

二. Mybatis-plus 租户

2.1 租户处理器

我们通过实现TenantLineHandler,即可实现对数据库中的表,进行租户过滤

属性名类型默认值描述
TenantLineHandlerTenantLineHandler租户处理器( TenantId 行级 )

实现租户过滤

  1. 这里我们通过匿名内部类实现TenantLineHandler。(也可以新建立一个类实现)
  2. 向MybatisPlusInterceptor添加我们所实现的匿名内部类
  3. 注入bean

ps:配置完后,就可实现对表进行租户筛选,读者们可自行测试

​ 由于后面我对该代码进行了加工,该代码会出现一些自定义逻辑在里面,但原理是不变的


/**
 * @author zhanghp
 * @since 2023/11/20 14:29
 */
@Configuration
public class TenantConfig {

	@Bean
	public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties properties) {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
			@Override
			public Expression getTenantId() {
        // 默认租户为1
				return new LongValue(1);
			}

			@Override
			public String getTenantIdColumn() {
				return properties.getTenantId();
			}

			// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
			@Override
			public boolean ignoreTable(String tableName) {
				return false;
			}
		}));
		return interceptor;
	}

}

2.2 前置准备

1. 依赖

各依赖版本继承至父依赖,可自行查看

     <dependencies>
        <!-- 启动 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 配置解析 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!-- mysql jdbc -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
        </dependency>
        <!-- 生成器模版引擎 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>
        <!-- alibaba thread local -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- hutool all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
    </dependencies>

2. 表及数据准备

完整配置:application.yml

这里我沿用了Spring自带的属性配置spring.sql.init,初始化数据库的schema,data。

建表sql | 数据

当启动项目后,自动进行建表,初始化表及相应的数据

spring.io官网对配置文件的概述:可自行搜索spring.sql.init解锁更多使用方式

  • 指定数据库配置
  • 指定出初始化的建表sql,及对应的数据
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: zhp.1221
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  sql:
    init:
      # 建表sql
      schema-locations: classpath:db/schema.sql
      # 数据
      data-locations: classpath:db/data.sql
      # 生成模式
      mode: always
      

3. 代码生成器

调用代码生成Generate.java,自动生成表实体类的相关类

这里就不进行展示了,自动代码生成若有不懂的地方可看Mybatis plus 自动生成代码与自定义模板

2.3 使用

🌳MpTest.java:测试对db的增删改查


/**
 * @author zhanghp
 * @since 2023/11/20 14:21
 */
public class MpTest extends AppTest {

	@Resource
	private DemoMapper demoMapper;

	@Test
	public void select() {
		demoMapper.selectList(null);
	}

	@Test
	public void insert() {
		demoMapper.insert(Demo.builder().id(100).age(18).name("草莓熊").build());
	}

	@Test
	public void update() {
		demoMapper.updateById(Demo.builder().id(100).age(20).name("皮卡").build());
	}

	@Test
	public void delete() {
		demoMapper.deleteById(Demo.builder().id(100).build());
	}

}

select查询结果日志:

image-20231124182325830

insert插入日志:

在这里插入图片描述

update修改日志:

在这里插入图片描述

delete删除日志:

在这里插入图片描述

三. 深入使用

3.1 前言

Saas多租户使用时,其实面临着许多技术问题,我拿几个公司所遇见的经典问题进行举例

  1. 如何传递tenat_id
  2. 有一些配置表,永远不需要租户过滤
  3. 在一个请求里,不需要租户过滤
  4. 在一个请求里,部分数据db需要租户过滤,部分数据db不需要租户过滤
  5. 微服务feign传递(由于我的代码项目是单体架构,我只简单赘述如何实现)

3.2 租户主体设值,取值

一个租户的设值,一般分为两种:

  1. 一个请求里,附带租户值,一般放在请求头(header)
  2. 自定义设值引用

设值后,需要找个地方存储,这里引用alibaba的TransmittableThreadLocal进行值的存储,也方便后续取值,所以这里新建立一个TenantContextHolder类

TENAN_ID:进行值的存储与读取

TENANT_SKIP:进行租户是否过滤,也就是对db是否进行租户过滤的判断(where tenant_id = ?),下一节会讲到

@UtilityClass:对所有方法加static,对类加final,不允许new


/**
 * @author zhanghp
 * @since 2023/11/20 20:28
 */
@Data
@UtilityClass
public class TenantContextHolder {

	/**
	 * tenant_id
	 */
	private final ThreadLocal<Long> TENAT_ID = new TransmittableThreadLocal<>();

	/**
	 * 获取租户
	 * @return 租户id
	 */
	public Long getTenantId() {
		return TENAT_ID.get();
	}

	/**
	 * 设置租户
	 * @param tenantId 租户
	 */
	public void setTenantId(Long tenantId) {
		TENAT_ID.set(tenantId);
	}
  
	/**
	 * 清空租户信息
	 */
	public void clear() {
		TENAT_ID.remove();
	}

}

​ 当有了这个类后,那我们需要重新实现TenantLineHandler,由于之前已经定义了一个TenantConfig的bean来对租户处理器并注入到容器中,若我们再次定义一个会导致bean冗余,让spring找不到使用哪个,进而导致启动报错。


那么问题来了,有没有一种方式我既要能控制新定义的TenangLineHandler存在,老的也兼容呢?

这时我们使用一下@ConditionalOnMissingBean使用在TenantConfig上,和@Order使用在DeepTenantConfig即可,读者可自行浏览

DeepTenantConfig

@ConditionalOnProperty(prefix = “custom.tenant”, value = “deep-config”, havingValue = “true”):
代表我是否要使用这个bean注入,为什么要有这个注解来控制bean注入呢?是因为要兼容上一章的租户使用TenantConfig,选择注入哪个,若没有该注解,则都会默认注入DeepTenantConfig的bean,控制该bean的注入在application.yml里的custom.tenant.deep-config


/**
 * @author zhanghp
 * @since 2023/11/24 17:51
 */
@Configuration
public class DeepTenantConfig {

    @Bean
    @Order(Integer.MIN_VALUE)
    @ConditionalOnProperty(prefix = "custom.tenant", value = "deep-config", havingValue = "true")
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                // 获取租户
                Long tenantId = TenantContextHolder.getTenantId();
                // 租户为空则返回空
                if (tenantId == null) {
                    return new NullValue();
                }
                // 返回租户
                return new LongValue(tenantId);
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                // 租户为空,则对该表不进行租户的操作
                Long tenantId = TenantContextHolder.getTenantId();
                if (tenantId == null) {
                    return Boolean.TRUE;
                }
                return false;
            }
        }));
        return interceptor;
    }

}

这些都处理好后,我们在配置中把custom.tenant.deep-config设置为true,这样读取的是我们上面定义的bean


我们在创建一个请求处理器类,进行租户的处理

TenantInterceptor

当一次请求进来,会进行租户值当处理,并存储在刚才我们所建立的TenantContextHolder类里,进行租户值处理


/**
 * @author zhanghp
 * @date 2023-11-25 13:48
 */
public class TenantInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       
        if (StrUtil.isNotBlank("tenant-id")) {
            TenantContextHolder.setTenantId(Convert.toLong(tenantId));
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        TenantContextHolder.clear();
    }
}

将拦截器添加到拦截器注册类里


/**
 * @author zhanghp
 * @date 2023-11-25 16:53
 */
@Configuration
public class CustomInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getRequestContextInterceptor());
    }

    @Bean
    public TenantInterceptor getRequestContextInterceptor() {
        return new TenantInterceptor();
    }
}


现在我们创建一个controller


/**
 * <p>
 * mybatis - demo表 前端控制器
 * </p>
 *
 * @author zhp
 * @since 2023-11-20
 */
@RestController
@RequestMapping("/zhanghp/demo")
public class DemoController {

    @Resource
    private DemoService demoService;

    @GetMapping("/get")
    public void getall(){
        List<Demo> all = demoService.getAll();
        if (IterUtil.isEmpty(all)) {
            return;
        }
        all.forEach(System.out::println);
    }
}

现在我们打开apifox/postman,进行一次请求操作,看是否会生效

  • http://localhost:8888/zhanghp/demo/get
  • 请求头添加tenant_id的值

发送请求
在这里插入图片描述

结果日志打印

在这里插入图片描述


3.3 部分表全量db操作

⭐️场景

其实在业务中,有一些配置表,字典表其实是不需要租户的,就比如城市配置表,城市就这些,加租户也是没有任何意义的

⭐️解决

实现其实也是很简单,只需要在DeepTenantConfig里的的ignoreTables方法进行一些逻辑处理就可以

实现步骤

  1. 在application.yml进行表的配置
  2. 读取配置,并在DeepTenantConfig添加自己的逻辑

这里由于读取了配置,我喜欢把自定义的配置定义一个类,通过该类对配置读取,读者可自行查看,这里就不赘述了

配置:

custom:
  tenant:
    # 是否注入deepTenantConfig的bean开关
    deep-config: true
    # 租户字段名称
    tenant-id: tenant_id
    # 忽略租户的表
    ignore-tables:
      - demo01
      - demo02

逻辑新增


/**
 * @author zhanghp
 * @since 2023/11/24 17:51
 */
@Configuration
public class DeepTenantConfig {

    @Bean
    @Order(Integer.MIN_VALUE)
    @ConditionalOnProperty(prefix = "custom.tenant", value = "deep-config", havingValue = "true")
    public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties properties) {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                // 获取租户
                Long tenantId = TenantContextHolder.getTenantId();
                // 租户为空则返回空
                if (tenantId == null) {
                    return new NullValue();
                }
                // 返回租户
                return new LongValue(tenantId);
            }

            @Override
            public String getTenantIdColumn() {
                return properties.getTenantId();
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                Long tenantId = TenantContextHolder.getTenantId();
                if (tenantId == null) {
                    return Boolean.TRUE;
                }
                // 忽略表对配置为空,则所有表都进行租户操作
                if (IterUtil.isEmpty(properties.getIgnoreTables())) {
                    return false;
                }
                // 指定的表永远不进行租户操作
                if (properties.getIgnoreTables().contains(tableName)) {
                    return true;
                }
                return false;
            }
        }));
        return interceptor;
    }
}

读者自行练习就可,就不再演示

3.4 全量db操作

⭐️场景

在一个请求里,这个请求涉及到的表是不需要租户过滤,那么读者就会想到,那在上一节的配置文件加啊。

这里的一个请求不需要对A表进行过滤,但另一个请求对A是需要进行租户过滤的,那这时加配置文件是行不通的

⭐️解决

  1. TenantContextHolder里定义一个过滤租户的threadlocal
  2. 在DeepTenantConfig的ignoreTables()方法新增逻辑判断
/**
 * @author zhanghp
 * @since 2023/11/20 20:28
 */
@Data
@UtilityClass
public class TenantContextHolder {

	/**
	 * tenant_id
	 */
	private final ThreadLocal<Long> TENAT_ID = new TransmittableThreadLocal<>();

	/**
	 * 租户过滤标识
	 */
	private final ThreadLocal<Boolean> TENANT_SKIP = new TransmittableThreadLocal<>();

	/**
	 * 获取租户
	 * @return 租户id
	 */
	public Long getTenantId() {
		return TENAT_ID.get();
	}

	/**
	 * 设置租户
	 * @param tenantId 租户
	 */
	public void setTenantId(Long tenantId) {
		TENAT_ID.set(tenantId);
	}

	/**
	 * 设置是否过滤的标识
	 */
	public void setTenantSkip() {
		TENANT_SKIP.set(Boolean.TRUE);
	}

	/**
	 * 获取是否跳过租户过滤的标识
	 * @return true-过滤 false-不过滤
	 */
	public Boolean getTenantSkip() {
		return TENANT_SKIP.get() != null && TENANT_SKIP.get();
	}

	/**
	 * 清空租户信息
	 */
	public void clear() {
		TENAT_ID.remove();
		clearSkip();
	}

	/**
	 * 清空租户过滤标识
	 */
	public void clearSkip(){
		TENANT_SKIP.remove();
	}

}

DeepTenantConfig


/**
 * @author zhanghp
 * @since 2023/11/24 17:51
 */
@Configuration
public class DeepTenantConfig {

    @Bean
    @Order(Integer.MIN_VALUE)
    @ConditionalOnProperty(prefix = "custom.tenant", value = "deep-config", havingValue = "true")
    public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties properties) {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                // 获取租户
                Long tenantId = TenantContextHolder.getTenantId();
                // 租户为空则返回空
                if (tenantId == null) {
                    return new NullValue();
                }
                // 返回租户
                return new LongValue(tenantId);
            }

            @Override
            public String getTenantIdColumn() {
                return properties.getTenantId();
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                // 1.是否跳过租户对该表对操作
                if (TenantContextHolder.getTenantSkip()) {
                    return Boolean.TRUE;
                }
                // 2.租户为空,则对该表不进行租户的操作
                Long tenantId = TenantContextHolder.getTenantId();
                if (tenantId == null) {
                    return Boolean.TRUE;
                }
                // 3.忽略表对配置为空,则所有表都进行租户操作
                if (IterUtil.isEmpty(properties.getIgnoreTables())) {
                    return false;
                }
                // 4.指定的表永远不进行租户操作
                if (properties.getIgnoreTables().contains(tableName)) {
                    return true;
                }
                return false;
            }
        }));
        return interceptor;
    }

}

在controller层新加一个TenantContextHolder.setTenantSkip()即可

@GetMapping("/get2")
    public void getall2(){
        TenantContextHolder.setTenantSkip();
        List<Demo> all = demoService.list();
        if (IterUtil.isEmpty(all)) {
            return;
        }
        all.forEach(System.out::println);
    }

读者自行练习即可

3.5 自由控制全量/租户对db操作

⭐️场景

在一个请求里,进行了对A表和B表操作,但是A表是不需要租户,而B表又是需要租户

⭐️解决

  1. 对A表的操作提取为一个a方法,对B的操作提取出一个b方法
  2. 利用上节的TENANT_SKIP让a方法跳过租户
  3. 当a方法执行后,利用切面进行TENANT_SKIP的清空
  4. 这时b方法就会带着租户对B表进行处理

这里我写了一个注解@TenanClear

作用域:方法,类

作用:执行当前方法里的所有db操作,都不带租户,通过属性globalFlag进行控制后续方法是否添加租户过滤

ps:是=实现原理在 TenantClearAspect

/**
 * @author zhanghp
 * @since 2023/11/21 20:41
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantClear {

    /**
     * <p>执行该方法后,是否全局过滤租户标志</p>
     * <ul>
     *     <li>true:执行完该线程之前,后续对db操作不添加租户操作</li>
     *     <li>false:执行完当前方法后,后续方法的db操作添加租户操作</li>
     * </ul>
     *
     * @return 默认后续不添加租户操作
     */
    boolean globalFlag() default true;
}

该注解的作用:当添加该注解后当前方法进行的所有db操作都不带租户处理,后续方法执行是否带租户,通过属性globalFlag决定


创建a方法,并创建b方法

在a方法添加@TenantClear(globalFlag = false),然后在controller层先调用a,在调用b

输出结果,a不带租户,b带租户


/**
 * <p>
 * mybatis - demo表 服务实现类
 * </p>
 *
 * @author zhp
 * @since 2023-11-20
 */
@Service
public class DemoServiceImpl extends ServiceImpl<DemoMapper, Demo> implements DemoService {

	@Override
	public void b() {
		super.list();
	}

	@Override
	@TenantClear(globalFlag = false)
	public void a(){
		super.list();
	}

}

在controller层调用

    @GetMapping("/tenant-clear")
    public void tenantClear(){
        Console.log("执行a方法");
        demoService.a();
        Console.log("执行b方法");
        demoService.b();
    }

结果打印

在这里插入图片描述

3.6 feign传递租户

由于本项目是单体项目,不涉及微服务,在这里只是简单概述

实现步骤:

  1. 实现feign自带的原生扩展RequestInterceptor类
  2. 重写apply方法,处理tenant-id的传递
public class CustomerFeignInterceptor implements RequestInterceptor {

    public CustomerFeignInterceptor() {
    }

    public void apply(RequestTemplate requestTemplate) {
        if (TenantContextHolder.getTenantId() == null) {
            log.debug("租户ID为空,feign不传递");
        } else {
            requestTemplate.header("tenant-id", new String[]{TenantContextHolder.getTenantId().toString()});
        }
    }
}

四. 源码地址

⭐️gitee:https://gitee.com/zhp1221/java/tree/master/lab_02_mybatis_plus/tenant

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

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

相关文章

C++11【上】

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 目录 &#x1f449;&#x1f3fb; 统一的列表初始化&#x1…

用AI工具3分钟整理并制作出一本书的思维导图

本期教大家快速用AI工具制作出精美的思维导图。 1.用ChatGPT总结出书本内容 首先打开ChatGPT&#xff0c;在对话框中输入你想要它生成的内容&#xff0c;并且要求他以markdown代码的格式输出&#xff0c;只需要几十秒的时间。整本书的框架思维导图就生成了&#xff0c;你还可以…

大一统模型 Universal Instance Perception as Object Discovery and Retrieval 论文阅读笔记

Universal Instance Perception as Object Discovery and Retrieval 论文阅读笔记 一、Abstract二、引言三、相关工作实例感知通过类别名进行检索通过语言表达式的检索通过指代标注的检索 统一的视觉模型Unified Learning ParadigmsUnified Model Architectures 四、方法4.1 Pr…

【论文解读】Edit-DiffNeRF:使用2D-扩散模型编辑3D-NeRF

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/abs/2306.09551 摘要 最近的研究表明&#xff0c;将预训练的扩散模型与神经辐射场&#xff08;NeRF&#xff09;相结合&#xff0c;是一种很有前途的文本到 3D 的生成…

“不得了·放飞杯” 2023年四川省健身健美锦标赛启动在成都隆重召开

“不得了放飞杯” 2023年四川省健身健美锦标赛启动在成都隆重召开 为了更好地推动四川省健身健美运动的普及和发展&#xff0c;结合《四川全民健身实施计划》的现状&#xff0c;适应新时代健身私教服务产业的发展需求&#xff0c;由中国健美协会指导&#xff0c;四川省健美健美…

使用SpringBoot集成FastDFS

使用SpringBoot集成FastDFS 这篇文章我们介绍如何使用 Spring Boot 将文件上传到分布式文件系统 FastDFS 中。 1、FastDFS FastDFS是一个开源的轻量级分布式文件系统&#xff0c;它对文件进行管理&#xff0c;功能包括&#xff1a;文件存储、文件同步、文件访问 &#xff0…

[修订版][工控]SIEMENS S7-200 控制交通红绿灯程序编写与分析

下载地址>https://github.com/MartinxMax/Siemens_S7-200_Traffic_Light 特别鸣谢接线过程实验目的题目要求I/O分配公式公式套用示例 程序分析分割块[不是必要的,自己分析用]左侧梯形图 [B1-B5]B1 [东西绿灯亮25s]B2 B3 B23 [东西绿灯闪烁3s]B4 [东西黄灯亮2s]B5 [东西红灯…

Labelme加载AI(Segment-Anything)模型进行图像标注

labelme是使用python写的基于QT的跨平台图像标注工具&#xff0c;可用来标注分类、检测、分割、关键点等常见的视觉任务&#xff0c;支持VOC格式和COCO等的导出&#xff0c;代码简单易读&#xff0c;是非常利用上手的良心工具。 第一步&#xff1a;   下载源码进行安装。 g…

float和double(浮点型数据)在内存中的储存方法

作者&#xff1a;元清加油 主页&#xff1a;主页 编译环境&#xff1a;visual studio 2022 (x86) 相信大家都知道数据在内存中是以二进制储存的 整数的储存方法是首位是符号位&#xff0c;后面便是数值位 那么浮点数在内存中是怎么储存的呢&#xff1f;我们先来看一个例子&am…

Python----函数的不定长参数--包裹位置参数*args、包裹关键字参数**kwargs

不定长参数 也叫 可变参数。用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。此时&#xff0c;可用包裹(packing)位置参数&#xff0c;或者包裹关键字参数&#xff0c;来进行参数传递&#xff0c;会显得非常方便。 相关链接&#xff1a;Python---函数的参数类型--…

带你用uniapp从零开发一个仿小米商场_1.环境搭建

uniapp 介绍 uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、Web&#xff08;响应式&#xff09;、以及各种小程序&#xff08;微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝&#xff09;、快应用等多个…

【Python篇】详细讲解正则表达式

文章目录 &#x1f339;什么是正则表达式&#x1f354;语法字符类别重复次数组合模式 ✨例子 &#x1f339;什么是正则表达式 正则表达式&#xff08;Regular Expression&#xff09;&#xff0c;简称为正则或正则表达式&#xff0c;是一种用于匹配、查找和操作文本字符串的工…

还在犹豫Harmony要不要学?已有170万人参加官方培训

鸿蒙这么火&#xff0c;要不要学&#xff1f; 两百强的 App厂商&#xff0c;大部分接受了与鸿蒙的合作&#xff0c;硬件也有非常多与鸿蒙合作的厂商。鸿蒙的合作企业基本已经覆盖整个互联网客户的主流需求&#xff1b;所以鸿蒙的崛起不过是早晚的问题。随着鸿蒙4.0的升级&…

Linxu 进程替换

进程替换的背景&#xff1a; 进程的替换我们需要调用execl这个接口,exxecl在3号手册&#xff0c;属于系统接口。 调用系统命令 execl 为了方便理解execl的作用&#xff0c;我们写一个程序&#xff1a; 单进程替换 我们发现运行结果是通过c库里的exec接口把系统命令 "l…

3 Unsupervised learning recommenders reinforcement learning

文章目录 Week1Unsupervised LearningClusteringK-meansprincipleOptimization objectiveInitializing K-meanschose the number of clusters Anomaly DetectionFind unusual eventsAlgorithmchose epsilonAnomally Detection vs Supervised learningfeatures Week2Recommender…

LeetCode Hot100 114.二叉树展开为链表

题目&#xff1a; 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…

力扣114. 二叉树展开为链表(java,用树模拟链表)

Problem: 114. 二叉树展开为链表 文章目录 题目描述思路解题方法复杂度Code 题目描述 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 1.展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左…

C/C++ 通过SQLiteSDK增删改查

SQLite&#xff0c;作为一款嵌入式关系型数据库管理系统&#xff0c;一直以其轻量级、零配置以及跨平台等特性而备受青睐。不同于传统的数据库系统&#xff0c;SQLite是一个库&#xff0c;直接与应用程序一同编译和链接&#xff0c;无需单独的数据库服务器进程&#xff0c;实现…

FFmpeg命令分隔视频

有一个视频如a.mp4&#xff0c;此视频采用帧率为30生成&#xff0c;共有299帧&#xff0c;这里通过FFmpeg命令分隔成1秒一个个的小视频&#xff0c;即每个小视频帧数为30帧。 用到的FFmpeg参数如下所示&#xff1a; (1).-i:指定输入视频文件的名称&#xff1b; (2).-c:指…

BUUCTF [MRCTF2020]Ez_bypass 1

题目环境&#xff1a;F12查看源代码 I put something in F12 for you include flag.php; $flagMRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}; if(isset($_GET[gg])&&isset($_GET[id])) { $id$_GET[id]; $gg$_GET[gg]; if (md5($id) md5($gg) && $id ! $gg) { …