当前位置: 首页 > news >正文

【Canal 中间件】Canal 实现 MySQL 增量数据的异步缓存更新

文章目录

    • 一、安装 MySQL
      • 1.1 启动 mysql 服务器
      • 1.2 开启 Binlog 写入功能
        • 1.2.1创建 binlog 配置文件
        • 1.2.2 修改配置文件权限
        • 1.2.3 挂载配置文件
        • 1.2.4 检测 binlog 配置是否成功
      • 1.3 创建账户并授权
    • 二、安装 RocketMQ
      • 2.1 创建容器共享网络
      • 2.2 启动 NameServer
      • 2.3 启动 Broker
      • 2.4 启动 rocketmq-console
    • 三、安装 canal
      • 3.1 启动容器
      • 3.2 查看日志
    • 四、安装 Redis
      • 4.1 启动 Redis
      • 4.2 使用 Another Redis Desktop Manager 客户端
    • 五、实现客户端代码
      • 5.1 导入依赖
      • 5.2 配置 application.yaml
      • 5.3 实现Canal同步服务代码
        • 5.3.1 Canal同步服务接口
        • 5.3.2 抽象Canal-RocketMQ通用处理服务
        • 5.3.3 具体类的同步服务实现
      • 5.4 实体类
      • 5.5 RocketMQ 消费者
      • 5.6 后续优化方案
    • 六、测试客户端代码
      • 6.1 创建数据库及表
      • 6.2 插入数据
    • 参考资料

一、安装 MySQL

QuickStart · alibaba/canal Wiki (github.com)

1.1 启动 mysql 服务器

docker run --name mysql-canal ^
-p 3306:3306 ^
-e MYSQL_ROOT_PASSWORD=root ^
-d mysql:5.7.36

1.2 开启 Binlog 写入功能

对于自建 MySQL容器 , 我们需要开启 Binlog 写入功能。

1.2.1创建 binlog 配置文件

在宿主机上创建 my.cnf 文件,配置 binlog-format 为 ROW 模式。my.cnf 的配置内容如下:

[mysqld]
# 开启 binlog
log-bin=mysql-bin 
# 选择 ROW 模式
binlog-format=ROW 
# 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
server_id=1 
1.2.2 修改配置文件权限

进入 MySQL 容器并修改 MySQL 容器配置文件 /etc/mysql/my.cnf 权限,以避免权限警告:

# 进入 MySQL 容器
$ docker exec -it mysql-canal bash# 修改文件权限
$ chmod 644 /etc/mysql/my.cnf
$ exit

注意,在没有修改配置文件并启动 MySQL 容器情况下,MySQL 会警告配置文件 /etc/mysql/my.cnf 权限设置不当,允许所有用户写入(world-writable)。由于安全原因,MySQL 会忽略这个配置文件

[Warning] World-writable config file '/etc/mysql/my.cnf' is ignored.
1.2.3 挂载配置文件

在 MySQL 容器运行后,使用以下命令将创建的 my.cnf 文件覆盖容器内的 /etc/mysql/my.cnf

# 覆盖配置文件
$ docker cp D:\Learning\java-demos\middleware-demos\spring-boot-canal\src\main\resources\conf\my.cnf mysql-canal:/etc/mysql/# 为了使新的配置生效,重启 MySQL 容器
$ docker restart mysql-canal

注意,MySQL 容器的 /etc/mysql/my.cnf 是一个符号链接,直接指定完整路径时会导致问题。

MySQL 启动时会首先加载主配置文件 /etc/mysql/my.cnf,然后加载 conf.d 目录下的所有配置文件。

1.2.4 检测 binlog 配置是否成功

进入 MySQL, 利用 show variables like 'log_bin'; 查看是否打开 binlog 模式:

$ docker exec -it mysql-canal bash# 查看挂载后的 my.cnf 文件
$ tail /etc/mysql/my.cnf# 查看 binlog 是否开启
$ mysql -uroot -proot
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
1 row in set (0.01 sec)# 查看 binlog 日志文件列表
mysql> show binary logs;# 查看正在写入的 binlog 文件
mysql> show master status;# 查看 Binlog 文件内容
mysql> mysqlbinlog /var/lib/mysql/mysql-bin.000001

1.3 创建账户并授权

授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant

# 进入 mysql 容器
$ docker exec -it mysql-canal mysql -uroot -proot# 创建用户名和密码都为 canal 的账户
mysql> CREATE USER canal IDENTIFIED BY 'canal';# 授予权限 GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
mysql> GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

二、安装 RocketMQ

2.1 创建容器共享网络

RocketMQ 中有多个服务,需要创建多个容器,创建 docker 网络便于容器间相互通信。

$ docker network create rocketmq

2.2 启动 NameServer

在 Docker 容器中运行 RocketMQ 的 NameServer 服务:

# 拉取RocketMQ镜像
$ docker pull apache/rocketmq:5.3.0# 启动 NameServer
$ docker run -d ^
-p 9876:9876  ^
--name rmqnamesrv-canal ^
--network rocketmq ^
apache/rocketmq:5.3.0 sh mqnamesrv# 验证 NameServer 是否启动成功
$ docker logs -f rmqnamesrv

2.3 启动 Broker

Brocker 部署相对麻烦一点,主要是在系统里面创建一个配置文件。然后,通过 docker 的 -v 参数使用 volume 功能,将本地配置文件映射到容器内的配置文件上。否则所有数据都默认保存在容器运行时的内存中,重启之后就又回到最初的起点。

(1) 新建配置文件 broker.conf

创建配置文件 broker.conf, 放到指定的目录 D:\Learning\java-demos\middleware-demos\spring-boot-canal-redis\src\main\resources\rocketmq\conf

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 10.8.12.174 # 此处为本地ip, 如果部署服务器, 需要填写服务器外网ip

注意,如果其他容器和本地客户端都要与该容器通信,不要使用 localhost 和 host.docker.internal ,可以使用 ipconfig 查看当前宿主机的 IP 地址。

(2) 启动容器

# 启动 Broker 和 Proxy
$ docker run -d ^
--name rmqbroker-canal ^
--network rocketmq ^
-p 10912:10912 -p 10911:10911 -p 10909:10909 ^
-p 8080:8080 -p 8081:8081 ^
-e "NAMESRV_ADDR=rmqnamesrv-canal:9876" ^
-e "JAVA_OPTS=-Duser.home=/opt" ^
-e "JAVA_OPT_EXT=-server -Xms512m -Xmx512m" ^
-v D:\Learning\java-demos\middleware-demos\spring-boot-canal-redis\src\main\resources\rocketmq\conf\broker.conf:/home/rocketmq/rocketmq-5.3.0/conf/broker.conf ^
apache/rocketmq:5.3.0 sh mqbroker --enable-proxy ^
-c /home/rocketmq/rocketmq-5.3.0/conf/broker.conf# 验证 Broker 是否启动成功
$ docker exec -it rmqbroker-canal bash -c "tail -n 10 /home/rocketmq/logs/rocketmqlogs/proxy.log"

(3)创建 Topic

进入 broker 容器,通过 mqadmin 创建 Topic。

# 进入名为rmqbroker的容器,并启动一个交互式的Bash shell
$ docker exec -it rmqbroker-canal bash # 在容器内部使用 mqadmin 工具创建名为 TestTopic 的主题配置
$ sh mqadmin updatetopic -t canal-test-topic -c DefaultCluster

2.4 启动 rocketmq-console

启动容器

docker run -d ^
--name rmqconsole-test ^
--network rocketmq ^
--link rmqnamesrv-canal:namesrv ^
-e "JAVA_OPTS=-Drocketmq.config.namesrvAddr=namesrv:9876 -Drocketmq.config.isVIPChannel=false" ^
-p 8088:8080 ^
-t pangliang/rocketmq-console-ng

运行成功,稍等几秒启动时间,浏览器输入 http://localhost:8088 查看。

三、安装 canal

3.1 启动容器

# 拉取 Canal Server 的 Docker 镜像
$ docker pull canal/canal-server:v1.1.7# 启动 Canal Server 容器
$ docker run -d ^--name canal-server ^--restart always ^-p 11111:11111 ^--privileged=true ^-e canal.destinations=test ^-e canal.serverMode=rocketMQ ^-e rocketmq.producer.group=my-producer_canal-test-topic ^-e rocketmq.namesrv.addr=host.docker.internal:9876 ^-e canal.instance.master.address=host.docker.internal:3306 ^-e canal.instance.filter.regex=test_db.users,.*\\..* ^-e canal.mq.topic=canal-test-topic ^-m 4096m ^canal/canal-server:v1.1.7

3.2 查看日志

在 canal 启动成功后,查看启动日志:

$ docker logs canal-server
2024-10-28 21:29:00 DOCKER_DEPLOY_TYPE=VM
2024-10-28 21:29:00 ==> INIT /alidata/init/02init-sshd.sh
2024-10-28 21:29:00 ==> EXIT CODE: 0
2024-10-28 21:29:00 ==> INIT /alidata/init/fix-hosts.py
2024-10-28 21:29:00 ==> EXIT CODE: 0
2024-10-28 21:29:00 ==> INIT DEFAULT
2024-10-28 21:29:00 ==> INIT DONE
2024-10-28 21:29:00 ==> RUN /home/admin/app.sh
2024-10-28 21:29:01 ==> START ...
2024-10-28 21:29:01 start canal ...
2024-10-28 21:29:00 Failed to get D-Bus connection: Operation not permitted
2024-10-28 21:29:00 Failed to get D-Bus connection: Operation not permitted
2024-10-28 21:29:36 start canal successful
2024-10-28 21:29:36 ==> START SUCCESSFUL ...

看到 successful 之后,就代表 canal-server 启动成功,然后就可以在 canal-admin 上进行任务分配了。

四、安装 Redis

4.1 启动 Redis

docker run ^
--restart=always ^
-p 6379:6379 ^
--name redis-canal ^
-d redis:latest  --requirepass 123456

4.2 使用 Another Redis Desktop Manager 客户端

在 github 上面下载 nother Redis Desktop Manager 客户端,并连接到 redis-canal。

在这里插入图片描述

五、实现客户端代码

5.1 导入依赖

创建 Spring Boot 项目,并导入以下依赖。

<dependency><groupId>com.alibaba.otter</groupId><artifactId>canal.client</artifactId><version>1.1.7</version>
</dependency><!-- Message、CanalEntry.Entry等来自此安装包 -->
<dependency><groupId>com.alibaba.otter</groupId><artifactId>canal.protocol</artifactId><version>1.1.7</version>
</dependency><dependency><groupId>org.rocketmq.spring.boot</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.3.0</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

5.2 配置 application.yaml

application.yaml 的内容如下:

spring:application:name: spring-boot-canal-redisdatasource:url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverdata:redis:host: localhostport: 6379database: 0password: "123456"lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: -1msrocketmq:name-server: localhost:9876producer:group: my-producer_canal-test-topicsend-message-timeout: 60000retry-times-when-send-failed: 2retry-times-when-send-async-failed: 2
#  consumer:
#    group: my-consumer_canal-test-topicserver:port: 8089

5.3 实现Canal同步服务代码

5.3.1 Canal同步服务接口
/***  Canal同步服务接口,用于处理来自Canal的数据同步请求*  该接口主要定义了如何处理数据变更事件,包括DDL语句执行和DML操作(插入、更新、删除)** @author zouhu* @data 2024-10-31 15:16*/
public interface CanalSyncService<T> {/*** 处理数据变更事件* <p>*     该方法用于处理来自Canal的数据变更事件,包括DDL语句执行和其他数据操作(如插入、更新和删除)* </p>** @param flatMessage CanalMQ数据*/void process(FlatMessage flatMessage);/*** DDL语句处理** @param flatMessage CanalMQ数据*/void ddl(FlatMessage flatMessage);/*** 插入** @param list 新增数据*/void insert(Collection<T> list);/*** 更新** @param list 更新数据*/void update(Collection<T> list);/*** 删除** @param list 删除数据*/void delete(Collection<T> list);
}
5.3.2 抽象Canal-RocketMQ通用处理服务
/*** 抽象Canal-RocketMQ通用处理服务*** @author zouhu* @data 2024-10-31 15:21*/
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractCanalRocketMqRedisService<T> implements CanalSyncService<T> {private final RedisTemplate<String, Object> redisTemplate;private Class<T> classCache;/*** 获取Model名称** @return Model名称*/protected abstract String getModelName();/*** 处理数据* <p>*     后续优化:可以使用策略模式来封装不同表的操作,不一定要统一* </p>** @param flatMessage CanalMQ数据*/@Overridepublic void process(FlatMessage flatMessage) {if (flatMessage.getIsDdl()) {ddl(flatMessage);return;}Set<T> data = getData(flatMessage);if (SqlType.INSERT.getType().equals(flatMessage.getType())) {insert(data);}if (SqlType.UPDATE.getType().equals(flatMessage.getType())) {update(data);}if (SqlType.DELETE.getType().equals(flatMessage.getType())) {delete(data);}}/*** DDL语句处理** @param flatMessage CanalMQ数据*/@Overridepublic void ddl(FlatMessage flatMessage) {//TODO : DDL需要同步,删库清空,更新字段处理}/*** 插入** @param list 新增数据*/@Overridepublic void insert(Collection<T> list) {insertOrUpdate(list);}/*** 更新** @param list 更新数据*/@Overridepublic void update(Collection<T> list) {insertOrUpdate(list);}/*** 删除** @param list 删除数据*/@Overridepublic void delete(Collection<T> list) {Set<String> keys = Sets.newHashSetWithExpectedSize(list.size());for (T data : list) {keys.add(getWrapRedisKey(data));}redisTemplate.delete(keys);}/*** 插入或者更新redis* <p>*     data 对象里面还包含 getTypeArgument()的返回值,但是没有写到 Redis 里面* </p>** @param list 数据*/private void insertOrUpdate(Collection<T> list) {for (T data : list) {log.info("redis data:{}", data);String key = getWrapRedisKey(data);log.info("redis key:{}", key);redisTemplate.opsForValue().set(key, data);}}/*** 封装redis的key** @param t 原对象* @return key*/protected String getWrapRedisKey(T t) {return getModelName() + ":" + getIdValue(t);}/*** 获取类泛型** @return 泛型Class*/@SuppressWarnings("unchecked")protected Class<T> getTypeArgument() {if (classCache == null) {classCache = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];}return classCache;}/*** 获取 Object 标有 @TableId 注解的字段值** @param t 对象* @return id值*/protected Object getIdValue(T t) {Field fieldOfId = getIdField();ReflectionUtils.makeAccessible(fieldOfId);return ReflectionUtils.getField(fieldOfId, t);}/*** 获取Class标有@TableId注解的字段名称** @return id字段名称*/protected Field getIdField() {Class<T> clz = getTypeArgument();Field[] fields = clz.getDeclaredFields();for (Field field : fields) {TableId annotation = field.getAnnotation(TableId.class);if (annotation != null) {return field;}}log.error("PO类未设置@TableId注解");throw new RuntimeException("PO类未设置@TableId注解");}/*** 转换 Canal 的 FlatMessage中的data成泛型对象** @param flatMessage Canal发送MQ信息* @return 泛型对象集合*/protected Set<T> getData(FlatMessage flatMessage) {List<Map<String, String>> sourceData = flatMessage.getData();Set<T> targetData = Sets.newHashSetWithExpectedSize(sourceData.size());for (Map<String, String> map : sourceData) {// 将Type类型的数据和T对象合并转换为泛型对象TT t = JSON.parseObject(JSON.toJSONString(map), getTypeArgument());targetData.add(t);}return targetData;}}
5.3.3 具体类的同步服务实现
/*** User类的 Canal-RocketMQ通用处理服务实现** @author zouhu* @data 2024-10-31 17:23*/
@Component
public class UserCanalRocketMqRedisService extends AbstractCanalRocketMqRedisService<User> {public UserCanalRocketMqRedisService(RedisTemplate<String, Object> redisTemplate) {super(redisTemplate);}@Overrideprotected String getModelName() {return "User";}
}

5.4 实体类

后续将根据这个实体类来进行测试。

/*** User 实体类** @author zouhu* @data 2024-10-31 13:29*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User extends Model<User> {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.AUTO)private Integer id;private String name;private String email;
}

5.5 RocketMQ 消费者

/*** 监听所有表的数据修改 binlog* <p>*     目前只实现了单个表的处理逻辑, 后续可以使用策略模式实现不同表的处理逻辑* </p>** @author zouhu* @data 2024-10-27 23:18*/
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(topic = "canal-test-topic",consumerGroup = "my-consumer_test-topic-1"
)
public class CanalCommonSyncBinlogConsumer implements RocketMQListener<FlatMessage> {private final UserCanalRocketMqRedisService userCanalRocketMqRedisService;@Overridepublic void onMessage(FlatMessage flatMessage) {log.info("consumer message {}", flatMessage);try {userCanalRocketMqRedisService.process(flatMessage);} catch (Exception e) {log.warn(String.format("message [%s] 消费失败", flatMessage), e);throw new RuntimeException(e);}}
}

5.6 后续优化方案

使用策略模式实现不同表的处理策略.

六、测试客户端代码

6.1 创建数据库及表

执行以下 sql 语句,创建数据库及表

CREATE DATABASE test_db;
USE test_db;CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100),email VARCHAR(100)
);

查看 /home/canal-server//logs/test/meta.log 日志文件,数据库的每次增删改操作,都会在meta.log中生成一条记录,查看该日志可以确认 Canal 是否有采集到数据。

查看客户端控制台输出的信息

2024-10-31T22:56:24.607+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_6] .z.s.c.r.m.CanalCommonSyncBinlogConsumer : consumer message FlatMessage [id=22, database=test_db, table=, isDdl=true, type=QUERY, es=1730386584000, ts=1730386584600, sql=CREATE DATABASE test_db, sqlType=null, mysqlType=null, data=null, old=null, gtid=]
2024-10-31T22:56:24.608+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_7] .z.s.c.r.m.CanalCommonSyncBinlogConsumer : consumer message FlatMessage [id=22, database=test_db, table=users, isDdl=true, type=CREATE, es=1730386584000, ts=1730386584600, sql=CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100),email VARCHAR(100)
), sqlType=null, mysqlType=null, data=null, old=null, gtid=]

6.2 插入数据

执行以下 sql 语句

INSERT INTO users (name, email) VALUES ('Alice8', 'alice@example.com');

查看客户端控制台输出的信息

2024-10-31T22:57:12.601+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_8] .z.s.c.r.m.CanalCommonSyncBinlogConsumer : consumer message FlatMessage [id=23, database=test_db, table=users, isDdl=false, type=INSERT, es=1730386632000, ts=1730386632592, sql=, sqlType={id=4, name=12, email=12}, mysqlType={id=INT, name=VARCHAR(100), email=VARCHAR(100)}, data=[{id=1, name=Alice8, email=alice@example.com}], old=null, gtid=]
2024-10-31T22:57:12.601+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_8] .r.c.s.AbstractCanalRocketMqRedisService : redis data:User(id=1, name=Alice8, email=alice@example.com)
2024-10-31T22:57:12.601+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_8] .r.c.s.AbstractCanalRocketMqRedisService : redis key:User:1

使用 another Redis Desktop Manager 客户端查看 Redis 是否更新

在这里插入图片描述

参考资料

Canal + RocketMQ 同步 MySQL 数据到 Redis 实战-一只小松徐吖

Canal Kafka RocketMQ QuickStart · alibaba/canal Wiki (github.com)

使用Canal和RocketMQ实现数据库变更订阅处理_云消息队列 RocketMQ 版(RocketMQ)-阿里云帮助中心

超详细的canal入门,看这篇就够了-阿里云开发者社区 (aliyun.com)

相关文章:

【Canal 中间件】Canal 实现 MySQL 增量数据的异步缓存更新

文章目录 一、安装 MySQL1.1 启动 mysql 服务器1.2 开启 Binlog 写入功能1.2.1创建 binlog 配置文件1.2.2 修改配置文件权限1.2.3 挂载配置文件1.2.4 检测 binlog 配置是否成功 1.3 创建账户并授权 二、安装 RocketMQ2.1 创建容器共享网络2.2 启动 NameServer2.3 启动 Broker2.…...

独立开发的个人品牌打造:个人IP与独立开发的结合

引言 个人品牌程序员也需要打造。在当今的创意经济中&#xff0c;个人IP与独立开发的结合成为了一种趋势&#xff0c;为个体带来了前所未有的机会和可能性。本文将探讨如何通过打造个人IP来增强独立开发的影响力&#xff0c;并探索这种结合为个人带来的潜在价值。 个人IP的重…...

每天一题:洛谷P2002 消息扩散

题目背景 本场比赛第一题&#xff0c;给个简单的吧&#xff0c;这 100 分先拿着。 题目描述 有 n 个城市&#xff0c;中间有单向道路连接&#xff0c;消息会沿着道路扩散&#xff0c;现在给出 n 个城市及其之间的道路&#xff0c;问至少需要在几个城市发布消息才能让这所有 …...

【深度学习】用LSTM写诗,生成式的方式写诗系列之一

Epoch 4: 100%|███████████████████████████████████████████████████████████| 63/63 [00:07<00:00, 8.85batch/s, acc18.5, loss5.8] [5] loss: 5.828, accuracy: 18.389 , lr:0.001000 Epoch 5: 100%|███…...

HomeAssistant自定义组件学习-【二】

#要说的话# 前面把中盛科技的控制器组件写完了。稍稍熟悉了一些HA&#xff0c;现在准备写窗帘控制组件&#xff0c;构想的东西会比较多&#xff0c;估计有些难度&#xff0c;过程会比较长&#xff0c;边写边记录吧&#xff01; #设备和场景环境# 使用的是Novo的电机&#xf…...

如何看待AI技术的应用前景?

文章目录 如何看待AI技术的应用前景引言AI技术的现状1. AI的定义与分类2. 当前AI技术的应用领域 AI技术的应用前景1. 经济效益2. 社会影响3. 技术进步 AI技术应用面临的挑战1. 数据隐私与安全2. 可解释性与信任3. 技能短缺与就业影响 AI技术的未来发展方向1. 人工智能的伦理与法…...

Unity中的屏幕坐标系

获得视口宽高 拖动视口会改变屏幕宽高数值 MousePosition 屏幕坐标系的原点在左下角&#xff0c;MousePosition返回Z为0也就是纵深为0的Vector3 但是如果鼠标超出屏幕范围不会做限制&#xff0c;所以可能出现负数或者大于屏幕宽高的情况&#xff0c;做鼠标拖拽物体时需要注…...

标题点击可跳转网页

要实现点击标题跳转到网页的功能&#xff0c;你可以在Vue组件中使用<a>标签&#xff08;锚点标签&#xff09;并设置href属性为网页的URL。如果你希望使用uni-app的特性来控制页面跳转&#xff0c;可以使用uni.navigateTo方法&#xff08;这适用于uni-app环境&#xff0c…...

易语言模拟真人动态生成鼠标滑动路径

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…...

Linux:生态与软件安装

文章目录 前言一、Linux下安装软件的方案二、包管理器是什么&#xff1f;三、生态问题相关的理解1. 什么操作系统是好的操作系统&#xff1f;2. 什么是生态&#xff1f;3. 软件包是谁写的&#xff1f;这些工程师为什么要写&#xff1f;钱的问题怎么解决? 四、我的服务器怎么知…...

R 语言与其他编程语言的区别

R 语言与其他编程语言的区别 R 语言作为一种专门用于统计计算和图形的编程语言&#xff0c;与其他编程语言相比有一些独特的特点和区别。本文将详细介绍这些区别&#xff0c;帮助你更好地理解 R 语言的优势和适用场景。 1. 专为统计和数据分析设计 统计功能 内置统计函数&…...

RC低通滤波器Bode图分析(传递函数零极点)

RC低通滤波器 我们使得R1K&#xff0c;C1uF&#xff1b;电容C的阻抗为Xc&#xff1b; 传递函数 H ( s ) u o u i X C X C R 1 s C 1 s C R 1 1 s R C &#xff08;其中 s j ω &#xff09; H(s)\frac{u_{o} }{u_{i} } \frac{X_{C} }{X_{C}R} \frac{\frac{1}{sC} }{\…...

基于深度学习的网络入侵检测

基于深度学习的网络入侵检测是一种利用深度学习技术对网络流量进行实时监测与分析的方法&#xff0c;旨在识别并防范网络攻击和恶意活动。随着网络环境日益复杂&#xff0c;传统的入侵检测系统&#xff08;IDS&#xff09;在面对不断变化的攻击模式时&#xff0c;往往难以保持高…...

《构建一个具备从后端数据库获取数据并再前端显示的内容页面:前后端实现解析》

一、前端页面&#xff1a;布局与功能 1. 页面结构 我们先来看前端页面的 HTML 结构&#xff0c;它主要由以下几个部分组成&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewp…...

Rust 力扣 - 59. 螺旋矩阵 II

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 使用一个全局变量current记录当前遍历到的元素的值 我们只需要一圈一圈的从外向内遍历矩阵&#xff0c;每一圈遍历顺序为上边、右边、下边、左边&#xff0c;每遍历完一个元素后current 我们需要注意的是如果上…...

2.4w字 —TS入门教程

目录 1. 什么是TS 2. TS基本使用 3 TS基础语法 3.1 基础类型约束 3.11 string&#xff0c;number&#xff0c;boolean&#xff0c; null和undefined 3.12 any 3.13 unknown 3.14 void 3.15 数组 3.16 对象 3.2 函数的约束 3.21 普通写法 3.22 函数表达式 3.22 可选…...

java: 未结束的字符文字 报错及解决:将编码全部改为UTF-8或者GBK

报错&#xff1a; 解决&#xff1a; 将编码都改成UTF-8或者GBK&#xff1a;...

Android平台RTSP转RTMP推送之采集麦克风音频转发

技术背景 RTSP转RTMP推送&#xff0c;好多开发者第一想到的是采用ffmpeg命令行的形式&#xff0c;如果对ffmpeg比较熟&#xff0c;而且产品不要额外的定制和更高阶的要求&#xff0c;未尝不可&#xff0c;如果对产品稳定性、时延、断网重连等有更高的技术诉求&#xff0c;比较…...

认证鉴权框架之—sa-token

一、概述 Satoken 是一个 Java 实现的权限认证框架&#xff0c;它主要用于 Web 应用程序的权限控制。Satoken 提供了丰富的功能来简化权限管理的过程&#xff0c;使得开发者可以更加专注于业务逻辑的开发。 二、逻辑流程 1、登录认证 &#xff08;1&#xff09;、创建token …...

Spring源码(十一):Spring MVC之DispatchServlet

本篇重点在于分析Spring MVC与Servlet标准的整合&#xff0c;下节将详细讨论Spring MVC的启动/加载流程、处理请求的具体流程。 一、介绍 Spring框架提供了构建Web应用程序的全功能MVC模块。通过策略接口 &#xff0c;Spring框架是高度可配置的&#xff0c;而且支持多种视图技…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...