更好用的Mybatis Plus:Mybatis Flex(下)

news2024/11/26 13:37:49

前言

上篇文章讲了 Mybaits Flex 的基础用法,这次讲一下 Mybatis Flex 的进阶用法,包含了一些 Mybatis Flex 核心的一些功能。(以下简称 MF

逻辑删除

上篇文章讲到了 @Column 注解,其中有个属性为 isLogicDelete ,当这个属性为 true 时,则标识该字段为逻辑删除字段,MF 会识别到该字段并进相应的一些处理。

image.png

当你在实体类中指明了逻辑删除字段后,在查询时 MF 会自动帮你拼接上 WHERE xxx = 0 的 SQL,而在你删除时则不会真正删除表中的数据,而是将你标识为逻辑删除的字段的值改为1,使其查询不到。

假如我们使用 MF 查询某张表:

@Test
    void simpleSelect() {
        List<SysUser> sysUsers = userMapper.selectAll();
        sysUsers.forEach(System.out::println);
    }

其执行 SQL 如下:

SELECT 
  * 
FROM 
  sys_user 
WHERE 
  deleted = 0

需要注意的是:当你进行连表查询时,如果你连的表中同样标明了逻辑删除字段也会作为条件拼接。

@Test
    void complexSelect() {
        List<SysUser> sysUserList = userMapper.selectListByQuery(QueryWrapper.create()
                        .select()
                        .from(SysUser.class)
                        .leftJoin(SysDept.class)
                        .on(SYS_USER.DEPT_ID.eq(SYS_DEPT.DEPT_ID))
                        .where(SYS_USER.USER_ID.gt(5)));
        sysUserList.forEach(System.out::println);
    }

执行 SQL 如下:

SELECT
  *
FROM
  sys_user
LEFT JOIN sys_dept 
  ON sys_dept.deleted = 0 
    AND sys_user.dept_id = sys_dept.dept_id
WHERE sys_user.user_id > 5 AND sys_user.deleted = 0

而当你删除数据时:

@Test
    void simpleDelete() {
        userMapper.deleteById(11);
    }

执行 SQL 如下:

UPDATE
  sys_user
SET
  deleted = 1
WHERE
  user_id = 11

乐观锁

当同时对同一条数据进行操作时,我们应该只允许其中一个操作成功,其余操作应该失败掉,而 MF 也提供了相应的解决方案。

@Column 中有一个 version 属性,当其设置为 true 时,MF 会将其视为乐观锁字段,在新增数据时如果该数据没设值,则会默认设为0。而当其进行修改时,会先比对版本是否正确,如果正确才会修改成功,修改成功后会将版本号+1。

image.png

UPDATE 
  account 
SET 
  xxx = xxx,
  version = version + 1
WHERE 
  id = ? 
  AND version = ?

需要注意的是:在同一张表中,只能有一个乐观锁字段,即:每个实体类中只能有一个被 @Column(version = true) 修饰的字段。

数据填充

当我们对某张表进行 Insert 或者 update 时,我们希望某些字段的值可以自动填充,此时就可以用到 MF 中提供的数据填充的功能,而在 MF 中提供了两种数据填充的方式:@Table 注解和 @Column 注解。

@Table 注解

@Table 注解中提供了 onInsert 属性和 onUpdate 属性来帮助我们自定义插入时的监听器。

image.png

@Table 注解中提供了三个属性来帮我们监听:插入修改设置,我们需要分别继承 MF 的:InsertListenerUpdateListenerSetListener 进行相应的操作,如下:

image.png

image.png

image.png

通过监听用户操作来帮我们完成一些额外的操作。

@Column 注解

@Column 注解中同样提供了数据填充的功能:onInsertValueonUpdateValue

image.png

类似于 Mybatis Plus 中 @TableField 注解中的 fill 属性,但是 MF 中的该功能会更强大,它甚至支持写 SQL 语句来填充值:

image.png

@Table 注解 与 @Column 注解的区别

在使用 @Table 注解中的数据填充功能时,我们设置的值是 基于Java层面的,在准备执行操作时会先触发监听器,监听器将操作执行完成之后,将参数传递至 ORM 进行处理。

而在使用 @Column 注解中的数据填充功能时,我们设置是基于 数据库层面的 ,我们设置的值会直接拼接到 SQL 中。

数据脱敏

在上一篇文章中我们讲了注解 @ColumnMask ,该注解是实现数据脱敏的核心注解,其仅有一个属性 value ,它指定了我们执行脱敏策略的名称。

image.png

而 MF 共提供了9种脱敏规则,如下:

image.png

  • MOBILE :手机号脱敏
  • FIXED_PHONE :固定电话脱敏
  • ID_CARD_NUMBER :身份证号脱敏
  • CHINESE_NAME :中文名脱敏
  • ADDRESS :地址脱敏
  • EMAIL :邮箱脱敏
  • PASSWORD :密码脱敏
  • CAR_LICENSE :车牌号脱敏
  • BANK_CARD_NUMBER :银行卡号脱敏

自定义脱敏规则

除此之外,我们还可以去自定义脱敏规则,我们利用内部提供的 MaskManager 来实现。

MaskManager.registerMaskProcessor("自定义规则名称",
        data-> {
          //进行脱敏操作
          //返回脱敏后的数据
          return data;
        });

取消脱敏

在某些特定场景下,我们可能需要得到未脱敏的原始数据来进行操作,例如用户登陆时验证用户名与密码是否正确等。

MaskManager 中提供了两种方式来实现该功能:execWithoutMask()skipMask()restoreMask() ,需要注意的是execWithoutMask() 方法执行后会自动还原脱敏,而 skipMask()restoreMask() 需要配套使用才能实现取消脱敏和还原脱敏的操作。

使用方法如下:

SysUserMapper mapper = ...;
List<SysUser> userList = MaskManager.execWithoutMask(mapper::selectAll);
//业务操作
//...

又或者使用以下方式来实现:

try {
    //取消脱敏
    MaskManager.skipMask();
    List<SysUser> userList = sysUserMapper.selectListByQuery(...);
    //业务操作
    //...
} finally {
    //还原脱敏
    MaskManager.retoreMask();
}

多数据源

在某些场景下我们可能会使用到多数据源的方式来实现我们的业务,设置方式如下:

mybatis-flex:
  datasource:
    db1:
      url: jdbc:mysql://127.0.0.1:3306/db1
      username: root
      password: root
    db2:
      url: jdbc:mysql://127.0.0.1:3306/db2
      username: root
      password: root

MF 中也提供了两种方式来帮我们切换数据源:

  1. DataSourceKey.use()DataSourceKey.clear()
  2. @UseDataSource("datasourceName")
    • 当该注解加在Mapper上时,则指定该Mapper执行的方法使用指定数据源
    • 当该注解加在Mapper方法上时,则指定该方法使用指定数据源
    • 当该注解加在实体类上时,则指定该实体相关的增删改查使用指定数据源

@DataSourceKey.use()

try {
    DataSourceKey.use("datasourceName");
    //业务操作
    //...
} finally {
    DataSourceKey.clear();
}

@UseDataSource(“datasourceName”)

Mapper

@UseDataSource("datasourceName") 注解加在Mapper上时,指明该Mapper中所有方法使用指定数据源。

@UseDataSource("db1")
public interface SysUserMapper extends BaseMapper<SysUser> {
    // Mapper方法
    //...
}

Mapper方法

@UseDataSource("datasourceName") 注解加在Mapper方法上时,指明该Mapper中的该方法使用指定数据源。

public interface SysUserMapper extends BaseMapper<SysUser> {

    @UseDataSource("db2")
    List<SysUser> selectAll();
}

实体类

@UseDataSource("datasourceName") 注解加在实体类上时,指明该实体类所有的增删改查操作使用指定数据源。

@Data
@Table("sys_user")
@UseDataSource("db3")
public class SysUser {
  //...
}

读写分离

读写分离是基于 多数据源 来实现的,我们通过一些操作使查询操作访问 数据库A ,而新增、修改、删除等操作在 数据库B 上进行。

假如我们有四个库:masterslave1slave2other ,我们的 增删改 操作在 master 数据源上进行,而查询操作随机在 slave1slave2 上进行,而在 other 库上我们进行一些特定操作。那我们需要进行以下操作:

首先我们要先在配置文件中指定我们的数据源。

mybatis-flex:
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/master
      username: root
      password: root
    slave1:
      url: jdbc:mysql://localhost:3306/slave1
      username: root
      password: root
    slave2:
      url: jdbc:mysql://localhost:3306/slave2
      username: root
      password: root
    other:
      url: jdbc:mysql://localhost:3306/other
      username: root
      password: root

然后我们就可以来写我们的分片策略:

public class MyShardingStrategy implements DataSourceShardingStrategy {
    @Override
    public String doSharding(String currentDataSourceKey, 
                             Object mapper, 
                             Method method,
                             Object[] methodArgs) {
        //对于other库的操作我们直接放行
        if("other".equals(currentDataSourceKey)) {
            return currentDataSourceKey;
        }

        //如果mapper方法以 insert、update、delete开头,则在master库中执行
        if(StringUtil.startWith(method.getName(), "insert", "update", "delete")) {
            return "master";
        }

        //除去以上方法均在 salve 库中执行
        return "slave*";
    }
}

动态表名

当用户在对数据进行操作时,向 MF 传入表名,它能够根据上下文信息(例如用户信息、应用信息等)动态修改当前的表。

适用场景如下:

  • 多租户系统,当不同租户拥有不同的表时可使用动态表名来进行操作
  • 分库分表,当我们为减轻数据库压力而使用分库分表时可使用动态表名来进行操作

首先在应用启动时,我们要调用 TableManager.setDynamicTableProcessor() 方法 来配置动态表名处理器即可,如下:

TableManager.setDynamicTableProcessor(new DynamicTableProcessor(){
    @Override
    public String process(String tableName){
        return tableName + "_1";
    }
});

我们配置完成后,在对数据库进行 增删改查 操作时,均会调用 process 方法,待获取到新的表名后再进行处理。

在某些情况下,我们想临时修改映射关系,而非通过 process 方法获取,我们可以进行以下操作:

try {
    TableManager.setHintTableMapping("sys_user", "sys_user_1");
    //业务处理
    ...
} finally {
    TableManager.clear();
}

除了动态表名之外,MF 还支持 动态Scheme(模式),与动态表名配置类似,如下:

TableManager.setDynamicSchemeProcessor(new DynamicSchemeProcessor() {
    @Override
    public String process(String schema) {
      return schema + "_1";
    }
});

注意:动态Scheme的配置,只对使用了 @Table(schema = "xxx") 注解的实体类有效。

而当我们使用 SpringBoot 时,可以直接通过写配置类的方式进行注入。

@Configuration
public class DynamicConfiguration {
    @Bean
    public DynamicTableProcessor dynamicTableProcessor() {
        DynamicTableProcessor processor = new ....;
        return processor;
    }

    @Bean
    public DynamicSchemaProcessor dynamicSchemaProcessor(){
        DynamicSchemaProcessor processor = new ....;
        return processor;
    }
}

数据权限

我们在系统中可能会遇到不同用户、不同角色或者不同部门通过同一个接口查询时,得到的结果却不同的业务场景。MF 提供了 数据权限 的功能来供我们自定义数据权限。

自定义数据方言 IDialect

在自定义数据方言中,我们可以通过重写 forSelectByQuery 方法来构建通过 QueryWrapper 来查询的方法。

public class MyDataPermissionDialect extends CommonsDialectImpl {

    @Override
    public String forSelectByQuery(QueryWrapper wrapper) {
        //此处可以获取当前用户信息、角色信息或部门信息
        //通过用户信息或部门信息给查询增加条件
        wrapper.and(...);
        return super.buildSelectSql(wrapper);
    }
}

重写IService方法

与 MP 中一样,MF也提供了 IService 接口以及默认的实现类,我们可以通过构建自己的 IServiceImpl 来实现该功能。

public class MyServiceImpl<M extends BaseMapper<T>, <T>) implements IService<T> {
    @Autowried
    protected M mapper;

    @Override
    public BaseMapper<T> getMapper() {
        return mapper;
    }

    @Override
  public List<T> list(QueryWrapper wrapper) {
      //此处可以获取当前用户信息、角色信息或部门信息
      //通过用户信息或部门信息给查询增加条件
      return IService.super.list(wrapper);
  }
}

字段权限

假如我们在某一张表中有多个字段,但是根据不同的用户、部门或角色查询,得到的结果是不同的,例如在 sys_user 表中存有用户的密码信息,但 password 只允许本人与超管查询,此时我们就可以用到 MF 提供的 字段权限 的功能。

上一篇文章我们介绍了 @Table 注解,该注解中有一个属性名为 onSet ,我们可以通过该属性来编写我们自定义的监听器,当我们查询时会先执行 SQL,当我们的 SQL 执行完之后会将结果集映射到实体类中,此时 mybaits 会调用实体类中的 setter 方法,我们的监听器就会生效,我们主动调用 setter 方法时监听器不会生效。

示例如下:

@Data
@Table(value = "sys_user", onSet = MyPasswordSetListener.class)
public class SysUser {
    @Id(keyType = KeyType.Auto)
    private Long userId;

    private String username;

    private String password;
    ...
}
public class MyPasswordSetListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        //查询用户是否拥有查询密码的权限
        boolean hasPasswordPermission = hasPasswordPermission();

        //若用户不拥有查询密码的权限则直接返回空
        if(Boolean.FALSE.equals(hasPasswordPermission)) {
            return null;
        }

        return value;
    }
}

字段加密

当我们在数据库表中的某个字段存储为明文,但是想在获取时返回的为加密内容,此时可用到 MF为我们提供的 字段加密 的功能。

同样的,我们的实现也是通过 @Table 中的 onSet 属性来实现的,如下:

@Data
@Table(value = "sys_user", onSet = MyFieldEncryptionListener.class)
public class SysUser {
    @Id(keyType = KeyType.Auto)
    private Long userId;

    private String username;

    private String password;
    ...
}
public class MyFieldEncryptionListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        if(null != value) {
            return encrypt(value);
        }
        return value;
    }
}

字典回写

假如我们有一张字典表,字典表里存着一些业务字段,例如存放着性别的值,1表示男,2表示女,其余表示未知,此时在新增或者修改时,我们需要根据该表中配置的信息对前端进行回显。

我们依旧是使用 @Table 中的 onSet 属性来实现,为了保证数据类型一致,我们需要新加一个业务字段来进行回显,而不是使用原有的字段进行回显。

@Data
@Table(value = "sys_dict")
public class SysDict {
    @Id(keyType = KeyType.Auto)
    private Long dictId;

    private String value;

    private Integer code;
}
@Data
@Table(value = "sys_user",onSet = MyEchoListener.class)
public class SysUser {
    @Id(keyType = KeyType.Auto)
    private Long userId;

    private int gender;

    @Column(ignore = true)
    private String genderStr;

    ...
}
public class MyEchoListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        SysUser sysUser = (SysUser) entity;
        if("gender".equals(property) && Objects.nonNull(value)) {
            String genderStr = getGenderStrByDict(property);
            sysUser.setGenderStr(genderStr);
        }
        return value;
    }
}

枚举属性

在某些场景下,我们可能不需要通过字典表去管理我们的状态、性别等,此时我们可以使用枚举进行管理。

在 MF 中提供了一个名为 EnumTypeandler 的内置处理器,当我们没有显示设置时就会默认使用该处理器,但是只使用该处理器只能将枚举中的名字保存至数据库,而当我们需要将枚举中的某个值存入数据库时,我们需要在枚举类中进行一些处理。

@Data
@Table("sys_user")
public class SysUser {
    @Id(keyType = KeyType.Auto)
    private Long userId;

    private MyEnum myEnum;
    ...
}
public enum MyEnum {
    NORMAL(1, "正常"),
    BLOCK_UP(2, "停用"),
    FREEZING(3, "冻结"),
    LOCK(4, "锁定");

    @EnumValue
    private int code;

    private String statusStr;

    MyEnum(int code, String statusStr) {
        this.code = code;
        this.statusStr = statusStr;
    }

    //getter
    ...
}

我们通过 @EnumValue 注解来指定枚举中的哪个属性将被作为值传入数据库,同样的,在读取时,MF 也会自动将数据库中查出来的值映射成枚举。

注意事项:

  • @EnumValue 注解标注的属性,要求必须被 public 修饰,或者拥有 getter 方法。
  • 当枚举中的属性配置了 @EnumValue 时,在 QueryWrapper 构建时,传入枚举,自动使用该值进行 SQL 参数拼接。如下:
QueryWrapper wrapper = QueryWrapper.create();
wrapper.select()
  .from(SysUser.class)
  .where(SYS_USER.MY_ENUM.eq(MyEnum.NORMAL));

其执行 SQL 如下:

SELECT
  *
FROM
  sys_user
WHERE
  my_enum = 1

多租户

简单来讲,多租户就是一个应用可以为多个用户提供服务,且用户与用户之间数据隔离互不干扰。多租户技术要求所有用户共用同一个数据中心,但能提供多个客户端相同甚至可定制化的服务,并且仍然可以保障客户的数据隔离。

多租户的数据隔离有许多种方案,但最为常见的是以列进行隔离的方式。MF 内置的正是通过指定的列(租户ID tenant_id)进行隔离的方案。

当我们使用 MF 的多租户功能时,我们需要进行2个步骤,示例如下:

  1. 通过 @Column(tenantId = true) 来标识租户列
@Data
@Table(value = "sys_user")
public class SysUser {
    @Id(keyType = KeyType.Auto)
    private Long userId;

    @Column(tenantId = true)
    private Long tenantId;
    ...
}
  1. 配置 TenantManagerTenantFactory
TenantManager.setTenantFactory(new TenantFactory() {
    @Override
    public Object[] getTenantIds() {
        return ...;
    }
});

TenantFactory

TenantFactory 主要的作用为获取当前的租户ID,并在 增删改查 时自动带上 TenantFactory “生产” 的数据。其内部方法如下:

image.png

getTenantIds 要求返回一个数组,原因有如下场景:

  • 场景1:租户对自己的数据进行增删改查,返回的 Object[] 数组只有租户自己的 ID 就可以了。
  • 场景2:租户可以对自己,以及其他租户(比如下级租户)的数据进行增删改查,那么要求返回的 Object[] 必须包含其他租户的 ID。比如某个数据列表, 除了显示租户自己的数据以外,还包含下级租户的数据,这种场景则要求 getTenantIds 返回多个值。
  • 场景3:忽略租户条件,由代码自定义条件查询,此项要求 getTenantIds 返回 null 或者 空数组。

需要注意的是: 在整个应用中,应该 只有一个 TenantFactory 实例,然后再通过其 getTenantIds() 方法里去获取当前的租户 ID。在 Spring 框架中,我们可以通过在 RequestContextHolder 中去获取当前的租户 ID。

public class MyTenantFactory implements TenantFactory {

    @Override
    public Object[] getTenantIds() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        Long tenantId = attributes.getAttribute("tenantId", RequestAttributes.SCOPE_REQUEST);
        return new Object[]{tenantId};
    }
}

我们自定义完成之后还要定义一个拦截来器获取 tenantId

public class MyTenantInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //通过 request 去获取租户 ID
        Long tenantId = getTenantIdByReuqest(request);

        //设置租户ID到 request 的 attribute
        request.setAttribute("tenantId", tenantId);

        return true;
    }
}

最后我们需要将拦截器注册到 Spring 容器中。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyTenantInterceptor());
    }
}

如果我们使用的是 SpringBoot 时,我们可以直接通过 @Configuration 来配置。

@Configuration
public class MyTenantConfig {
    @Bean
    public TenantFactory tenantFactory() {
        return new MyTenantFactory();
    }
}

忽略多租户

在某些场景下,我们可能需要忽略多租户来对数据库进行操作,此时我们可以使用 TenantManager 中的 withoutTenantCondition 方法来完成。

SysUserMapper mapper = ...;
List<SysUser> userList = TenantManager.withoutTenantCondition(mapper::selectAll);

我们来看一下 withoutTenantCondition 方法的内部实现方式,如下:

image.png

里面通过调用 ignoreTenantCondition 方法和 restoreTenantCondition 方法来完成多租户的忽略与恢复。

那么我们也可以通过 ignoreTenantConditionrestoreTenantCondition 方法来完成代码比较复杂的情况,需要注意的是:ignoreTenantCondition 方法和 restoreTenantCondition 方法需要配套使用

try {
    TenantManager.ignoreTenantCondition();
    //相应的业务操作
    ...
} finally {
    TenantManager.restoreTenantCondition();
}

当然,除了以上两种情况之外,假如 TenantFactory 返回为空的情况下也会忽略多租户。

全局配置

除了单独配置完,我们也可以通过配置 FlexGlobalConfig 来实现多租户的全局配置。

FlexGlobalConfig.getDefaultConfig().setTenantColumn("tenant_id");

当我们使用此方法进行全局配置时,可省略 @Column(tenantId = true) 的注解。

注意事项

需要注意的是:当我们在新增时,如果实体类中标注了多租户字段,那么我们对这个字段设置任何值,最终都会被 TenantFactory 返回的内容所覆盖,假如 TenantFactory 返回的是一个数组,那么默认会取第一个值去填充,如果 TenantFacotry 返回为空或 null ,则会保留设置的值传入数据库。

示例如下:

SysUser user = new SysUser();
user.setTenantId(999);
userMapper.insert(user);

对于以上代码,会有两种情况:

  1. TenantFactory 返回的值不为空时,则默认取第一个值给 tenantId
  2. TenantFactory 返回的值为空或 null 时,tenantId 的值为 999

而当我们对实体类中标注了多租户的字段时,其所有通过 MF 来进行的查询、修改和删除均会带上租户的条件,执行 SQL 示例如下:

DELETE FROM
  sys_user
WHERE
  user_id = ?
  AND tenant_id = ?

TenantFactory 返回的租户ID为数组时,执行 SQL 如下:

DELETE FROM
  sys_user
WHERE
  user_id = ?
  AND tenant_id IN (?,?,...,?);

修改与查询同上所述。

总结

总的来说,Mybatis-Flex 为我们提供了许多的方法与功能供我们扩展,相对来说还是很强大的,希望文章能够帮助到各位,感谢观看。


推荐

关注博客和公众号获取最新文章

Bummon’s Blog | Bummon’s Home | 公众号

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

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

相关文章

2023软工作业(一)——计算器

班级班级社区作业要求软件工程实践第一次作业-CSDN社区作业目标完成一个具有可视化界面的科学计算器参考文献Fyne 目录 作业要求 项目源码地址 作业目标 0. 界面及功能展示 1. PSP表格 2. 解题思路描述 3. 核心代码 4. 设计与实现过程 5. 程序性能改进 6. 单元测试展…

向华为学习:制订一份分工明确、能够落地的产品GTM行动计划表

昨天华研荟介绍了新产品上市的GTM的定义、GTM这个岗位&#xff08;角色&#xff09;的主要工作以及新产品的GTM要回答好的四个问题&#xff08;Why、What、How、Where&#xff09;&#xff0c;帮助大家初步理解了让产品上市更成功的GTM流程。 如我昨天在文章中所讲到的&#x…

ACM MM 2023 | 基于去中心化表征的人体姿态估计方法

01. 前言 北京邮电大学与EVOL创新团队共同提出人体姿态估计方法DecenterNet&#xff0c;用于在提高在拥挤场景下人体姿态估计的准确度。该方法引入了一种去中心化的姿势表征方法&#xff0c;使得网络在纠缠区域/拥挤区域中将更加稳健地表达人体姿态。该方法还提出了一个解耦的…

LeetCode【577. 员工奖金】

表&#xff1a;Employee ---------------------- | Column Name | Type | ---------------------- | empId | int | | name | varchar | | supervisor | int | | salary | int | ---------------------- empId 是该表中具有唯一值的列。 该…

BFS专题7 多终点迷宫问题

题目&#xff1a; 样例&#xff1a; 输入 3 3 0 0 0 1 0 0 0 1 0 输出 0 1 2 -1 2 3 -1 -1 4 思路&#xff1a; 单纯的 BFS 迷宫问题&#xff0c;只是标记一下每个点的 step&#xff0c;注意初始化答案数组都为 -1. 代码详解如下&#xff1a; #include <iostream> #…

Windows下使用pybind11教程(python调用C++代码)

1. 下载pybind11 gittub中下载&#xff0c;pybind下载后解压 2. C生成库文件 2.1.VS新建空白工程&#xff0c;工程名随意起 - 2.2更改目标文件名和配置类型 - 2.3更改目标文件拓展名 2.4添加include路径和库路径 包含目录中添加刚刚下载好的pybind的include路径以及pyhon的…

C#的HALCON引擎调用_传入参数输出结果实现流程

1、在Halcon的开发环境里面写处理流程。 此案例使用HALCON自带图片&#xff1a; read_image (Image, printer_chip/printer_chip_01) 读入图片之后&#xff0c;做处理流程&#xff1a; *图像处理流程&#xff1a; *传入图像变量&#xff0c;阈值最小值&#xff0c;最大值。…

软件测试/测试开发丨利用人工智能自动找Bug

点此获取更多相关资料 简介 在程序员编程的过程中&#xff0c;产生Bug是一件平常的事情&#xff0c;以前在编码的过程中提前找出Bug&#xff0c;需要通过单元测试、CodeReview等各种方式。 当今&#xff0c;人工智能技术的发展给软件开发和测试带来了许多机会。利用人工智能…

聊聊并发编程——多线程之AQS

目录 队列同步器&#xff08;AQS&#xff09; 独占锁示例 AQS之同步队列结构 解析AQS实现 队列同步器&#xff08;AQS&#xff09; 队列同步器AbstractQueuedSynchronizer&#xff08;以下简称同步器&#xff09;&#xff0c;是用来构建锁或者其他同步组 件的基础框架&…

TikTok美国市场爆品:美牙仪一周售出3.36万单,GMV近百万刀

最近一周&#xff0c;超店有数洞察到TikTok Shop美国市场出现一款爆火美牙仪&#xff0c;该款商品售价为31.95美金&#xff0c;佣金比率为25%&#xff0c;一周内销量达3.36万单&#xff0c;GMV近94万美金。自今年7月底上架以来在TikTok上关联视频播放量高达140W&#xff0c;属于…

企业长假期间如何应对突发业务需求?提前部署远程控制为上策

没有人想在长假期间加班&#xff0c;包括管理层也是一样的。但客观来说&#xff0c;很多企业的业务在假期中也是不能中断的&#xff0c;如果业务线遇到紧急需要处理的问题&#xff0c;有没有办法不用长途跋涉跑回公司一趟呢&#xff1f;远程控制现在就是很多企业的选择。 时值…

静态住宅代理是什么?为什么要选择它?

静态住宅代理是互联网服务提供商(ISP)分配的住宅ISP代理。正如名称“静态”所指&#xff0c;他的IP永久不会变化。在当今的数字时代&#xff0c;数据安全、隐私和在线访问已变得至关重要&#xff0c;具有无限带宽的静态住宅代理提供了出色的解决方案。下面给大家具体介绍。 一、…

Django实战项目-学习任务系统-需求说明

一&#xff0c;需求说明   在我最近的阅读中&#xff0c;我深深被一些关于智能或系统的小说吸引。这些小说的主角意外获得某种神秘的智能或系统&#xff0c;然后通过完成系统发布的各种任务&#xff0c;逐渐提升自己的知识和能力。即使是普通的屌丝&#xff0c;也能在系统的管…

win10环境mysql8.10免安装版本配置

MySQL :: Download MySQL Community Server 下载免安装包 解压到相应目录。 以管理员身份启动cmd net start mysql 服务无法启动。 运行mysqld --initialize --console初始化 生成临时密码 验证临时密码并登录测试 mysql -u root -p出错 启动 net start mysql 运行phpmya…

Leetcode684. 冗余连接

Every day a Leetcode 题目来源&#xff1a;684. 冗余连接 解法1&#xff1a;并查集 因为需要判断是否两个节点被重复连通&#xff0c;所以我们可以使用并查集来解决此类问题。 代码&#xff1a; /** lc appleetcode.cn id684 langcpp** [684] 冗余连接*/// lc codestart…

PID温度控制器,全球市场总体规模,前17大厂商排名及市场份额

PID温度控制器全球市场总体规模 PID温度控制器是一种常用的温度控制设备&#xff0c;能够通过使用比例、积分和微分控制算法来实现精确的温度调节。它可以监测和调整温度&#xff0c;保持设定的温度稳定。PID代表比例、积分和微分&#xff0c;比例&#xff08;P&#xff09;控…

【Verilog教程】6.7 Verilog流水线

关键词&#xff1a;流水线&#xff0c;乘法器 硬件描述语言的一个突出优点就是指令执行的并行性。多条语句能够在相同时钟周期内并行处理多个信号数据。 但是当数据串行输入时&#xff0c;指令执行的并行性并不能体现出其优势。而且很多时候有些计算并不能在一个或两个时钟周期…

linux权限机制,

目录 用户与组,id,passwd 查看登录用户whomi,who,w 创建用户 useradd 修改用户信息usermod 删除指定用户userdel 组 ​编辑创建修改删除组groupadd groupmod groupdel 权限 ls-l 修改文件所属用户&#xff0c;所属组 chown,chgrp(change group) 修改权限 chmod 默认权…

针对http接口进行测试,使用Jmeter工具实现

前言&#xff1a; 本文主要针对http接口进行测试&#xff0c;使用Jmeter工具实现。 Jmter工具设计之初是用于做性能测试的&#xff0c;它在实现对各种接口的调用方面已经做的比较成熟&#xff0c;因此&#xff0c;本次直接使用Jmeter工具来完成对Http接口的测试。 一、开发接口…

26381-2011 合成纤维丝织坯绸 阅读笔记

声明 本文是学习GB-T 26381-2011 合成纤维丝织坯绸. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了合成纤维丝织坯绸的术语和定义、要求、试验方法、检验规则、包装和标志。 本标准适用于评定各类合成纤维丝织坯绸品质。 2 规…