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

springboot对接rabbitmq并且实现动态创建队列和消费

背景

1、对接多个节点上的MQ(如master-MQ,slave-MQ),若读者需要自己模拟出两个MQ,可以部署多个VM然后参考 docker 安装rabbitmq_Steven-Russell的博客-CSDN博客

2、队列名称不是固定的,需要接受外部参数,并且通过模板进行格式化,才能够得到队列名称

3、需要在master-MQ上延迟一段时间,然后将消息再转发给slave-MQ

问题

1、采用springboot的自动注入bean需要事先知道队列的名称,但是队列名称是动态的情况下,无法实现自动注入

2、mq弱依赖,在没有master-mq或者slave-mq时,不能影响到现有能力

解决方案

1、由于mq的队列创建、exchange创建以及队列和exchange的绑定关系是可重入的,所以采用connectFactory进行手动声明

2、增加自定义条件OnMqCondition,防止不必要的bean创建

总体流程

实施过程

搭建springboot项目

参考 搭建最简单的SpringBoot项目_Steven-Russell的博客-CSDN博客

引入amqp依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

引入后续会用到的工具类依赖

<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope>
</dependency>
<dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.40</version>
</dependency>

创建配置文件

在application.yml中增加如下配置

mq:master:addresses: 192.168.30.128:5672username: guestpassword: guestvhost: /slave:addresses: 192.168.30.131:5672username: guestpassword: guestvhost: /

创建自定义Condition注解和注解实现

package com.wd.config.condition;import org.springframework.context.annotation.Conditional;import java.lang.annotation.*;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMqCondition.class)
public @interface MqConditional {String[] keys();}
package com.wd.config.condition;import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.lang.NonNull;
import org.springframework.util.ObjectUtils;import java.util.Map;public class OnMqCondition implements Condition {@Overridepublic boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MqConditional.class.getName());if (annotationAttributes == null || annotationAttributes.isEmpty()) {// 为空则不进行校验了return true;}String[] keys = (String[])annotationAttributes.get("keys");for (String key : keys) {String property = context.getEnvironment().getProperty(key);if (ObjectUtils.isEmpty(property)) {return false;}}return true;}
}

创建多个链接工厂connectFactory

package com.wd.config;import com.wd.config.condition.MqConditional;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;@Configuration
public class MqConnectionFactory {@Value("${mq.master.addresses}")private String masterAddresses;@Value("${mq.master.username}")private String masterUsername;@Value("${mq.master.password}")private String masterPassword;@Value("${mq.master.vhost}")private String masterVhost;@Value("${mq.slave.addresses}")private String slaveAddresses;@Value("${mq.slave.username}")private String slaveUsername;@Value("${mq.slave.password}")private String slavePassword;@Value("${mq.slave.vhost}")private String slaveVhost;@Bean@Primary@MqConditional(keys = {"mq.master.addresses", "mq.master.vhost", "mq.master.username", "mq.master.password"})public ConnectionFactory masterConnectionFactory() {return doCreateConnectionFactory(masterAddresses, masterUsername, masterPassword, masterVhost);}@Bean@MqConditional(keys = {"mq.slave.addresses", "mq.slave.vhost", "mq.slave.username", "mq.slave.password"})public ConnectionFactory slaveConnectionFactory() {return doCreateConnectionFactory(slaveAddresses, slaveUsername, slavePassword, slaveVhost);}private ConnectionFactory doCreateConnectionFactory(String addresses,String username,String password,String vhost) {CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();cachingConnectionFactory.setAddresses(addresses);cachingConnectionFactory.setUsername(username);cachingConnectionFactory.setPassword(password);cachingConnectionFactory.setVirtualHost(vhost);return cachingConnectionFactory;}}

创建交换机名称枚举 DeclareQueueExchange

package com.wd.config;public enum DeclareQueueExchange {EXCHANGE("exchange"),DEAD_EXCHANGE("deadExchange"),DELAY_EXCHANGE("delayExchange");private final String exchangeName;DeclareQueueExchange(String exchangeName) {this.exchangeName = exchangeName;}public String getExchangeName() {return exchangeName;}
}

创建消息队列模板枚举 DeclareQueueName

package com.wd.config;public enum DeclareQueueName {DELAY_QUEUE_NAME_SUFFIX("_delay"),DEAD_QUEUE_NAME_SUFFIX("_dead"),QUEUE_NAME_TEMPLATE("wd.simple.queue.{0}");private final String queueName;DeclareQueueName(String queueName) {this.queueName = queueName;}public String getQueueName() {return queueName;}
}

创建消息VO和消息

package com.wd.controller.vo;import com.wd.pojo.Phone;
import lombok.Data;@Data
public class DelayMsgVo {private String queueId;private Phone phone;
}
package com.wd.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;
import java.util.List;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Phone implements Serializable {private static final long serialVersionUID = -1L;private String id;private String name;private Date createTime;private List<User> userList;}
package com.wd.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = -1L;private String username;private Date create;
}

定义队列id列表缓存,用于替换三方缓存,用于队列名称模板初始化

package com.wd.config;import java.util.ArrayList;
import java.util.List;public interface QueueIdListConfig {/*** 先用本地缓存维护队列id*/List<Integer> QUEUE_ID_LIST = new ArrayList<Integer>() {{add(111);add(222);add(333);}};
}

创建消息接受入口 controller

注意:此处就以web用户输入为入口,所以创建controller

package com.wd.controller;import com.alibaba.fastjson2.JSONObject;
import com.rabbitmq.client.*;
import com.wd.config.DeclareQueueExchange;
import com.wd.config.DeclareQueueName;
import com.wd.controller.vo.DelayMsgVo;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;@RestController
@ConditionalOnBean(value = ConnectionFactory.class, name = "masterConnectionFactory")
public class DynamicCreateQueueController {private final ConnectionFactory masterConnectionFactory;public DynamicCreateQueueController(@Qualifier(value = "masterConnectionFactory") ConnectionFactory masterConnectionFactory) {this.masterConnectionFactory = masterConnectionFactory;}@PostMapping(value = "sendDelayMsg")public String sendMsg2DelayQueue(@RequestBody DelayMsgVo delayMsgVo) throws IOException, TimeoutException {doSendMsg2DelayQueue(delayMsgVo);return "success";}private void doSendMsg2DelayQueue(DelayMsgVo delayMsgVo) throws IOException, TimeoutException {// 根据id 动态生成队列名称String queueNameTemplate = DeclareQueueName.QUEUE_NAME_TEMPLATE.getQueueName();String queueName = MessageFormat.format(queueNameTemplate, delayMsgVo.getQueueId());String delayQueueName = queueName + DeclareQueueName.DELAY_QUEUE_NAME_SUFFIX.getQueueName();String deadQueueName = queueName + DeclareQueueName.DEAD_QUEUE_NAME_SUFFIX.getQueueName();// 注意:下述声明交换机和队列的操作是可以重入的,MQ并不会报错try (Connection connection = masterConnectionFactory.createConnection();Channel channel = connection.createChannel(false)){// 声明死信交换机channel.exchangeDeclare(DeclareQueueExchange.DEAD_EXCHANGE.getExchangeName(), BuiltinExchangeType.DIRECT);// 声明死信队列AMQP.Queue.DeclareOk deadQueueDeclareOk = channel.queueDeclare(deadQueueName,true, false, false, null);// 定时任务 绑定消费者,避免出现多个消费者以及重启后无法消费存量消息的问题//  注意:因为需要保证消费顺序,所以此处仅声明一个消费者// 死信队列和交换机绑定channel.queueBind(deadQueueName, DeclareQueueExchange.DEAD_EXCHANGE.getExchangeName(), deadQueueName);// 声明延迟队列Map<String, Object> args = new HashMap<>();//设置延迟队列绑定的死信交换机args.put("x-dead-letter-exchange", DeclareQueueExchange.DEAD_EXCHANGE.getExchangeName());//设置延迟队列绑定的死信路由键args.put("x-dead-letter-routing-key", deadQueueName);//设置延迟队列的 TTL 消息存活时间args.put("x-message-ttl", 10 * 1000);channel.queueDeclare(delayQueueName, true, false, false, args);channel.exchangeDeclare(DeclareQueueExchange.DELAY_EXCHANGE.getExchangeName(), BuiltinExchangeType.DIRECT);channel.queueBind(delayQueueName, DeclareQueueExchange.DELAY_EXCHANGE.getExchangeName(), delayQueueName);// 发送消息到延迟队列channel.basicPublish(DeclareQueueExchange.DELAY_EXCHANGE.getExchangeName(), delayQueueName, null,JSONObject.toJSONString(delayMsgVo.getPhone()).getBytes(StandardCharsets.UTF_8));}}}

创建master延迟消息消费者

package com.wd.mq.consumer;import com.rabbitmq.client.*;
import com.wd.config.DeclareQueueExchange;
import com.wd.config.DeclareQueueName;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;import java.io.IOException;
import java.util.concurrent.TimeoutException;/*** 死信消费者,消费消息转发给targetConnectionFactory对应的目标MQ*/
public class MasterDeadQueueConsumer extends DefaultConsumer {private final ConnectionFactory targetConnectionFactory;public MasterDeadQueueConsumer(Channel channel, ConnectionFactory targetConnectionFactory) {super(channel);this.targetConnectionFactory = targetConnectionFactory;}@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {// 从死信队列的名称中截取队列名称,作为后续队列的名称String routingKey = envelope.getRoutingKey();String targetQueueName = routingKey.substring(0, routingKey.length() - DeclareQueueName.DEAD_QUEUE_NAME_SUFFIX.getQueueName().length());try (Connection targetConnection = targetConnectionFactory.createConnection();Channel targetChannel = targetConnection.createChannel(false)){// 声明交换机和队列targetChannel.exchangeDeclare(DeclareQueueExchange.EXCHANGE.getExchangeName(), BuiltinExchangeType.DIRECT);targetChannel.queueDeclare(targetQueueName, true, false, false, null);targetChannel.queueBind(targetQueueName, DeclareQueueExchange.EXCHANGE.getExchangeName(), targetQueueName);// 转发消息targetChannel.basicPublish(DeclareQueueExchange.EXCHANGE.getExchangeName(), targetQueueName, properties, body);} catch (TimeoutException e) {e.printStackTrace();// 注意此处获取的源队列的channelgetChannel().basicNack(envelope.getDeliveryTag(), false, true);}// 注意此处获取的源队列的channelgetChannel().basicAck(envelope.getDeliveryTag(), false);}
}

创建slave队列消息消费者

package com.wd.mq.consumer;import com.alibaba.fastjson2.JSONObject;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.wd.pojo.Phone;import java.io.IOException;public class SlaveQueueConsumer extends DefaultConsumer {public SlaveQueueConsumer(Channel channel) {super(channel);}@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {Phone phone = JSONObject.parseObject(new String(body), Phone.class);System.out.println("SlaveQueueConsumer consume ==> " + phone);getChannel().basicAck(envelope.getDeliveryTag(), false);}
}

创建定时任务,消费延迟消息

注意:因为采用的是死信队列的方式实现的延迟效果,此处只需要消费对应的死信队列即可

package com.wd.mq.quartz;import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.wd.config.DeclareQueueExchange;
import com.wd.config.DeclareQueueName;
import com.wd.config.QueueIdListConfig;
import com.wd.mq.consumer.MasterDeadQueueConsumer;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;import java.io.IOException;
import java.text.MessageFormat;
import java.util.concurrent.TimeoutException;@Configuration
@ConditionalOnBean(value = ConnectionFactory.class, name = {"slaveConnectionFactory", "masterConnectionFactory"})
public class MasterDeadQueueSubscribeProcessor {private final ConnectionFactory masterConnectionFactory;private final ConnectionFactory slaveConnectionFactory;public MasterDeadQueueSubscribeProcessor(@Qualifier(value = "masterConnectionFactory") ConnectionFactory masterConnectionFactory,@Qualifier(value = "slaveConnectionFactory") ConnectionFactory slaveConnectionFactory) {this.masterConnectionFactory = masterConnectionFactory;this.slaveConnectionFactory = slaveConnectionFactory;}/*** 消费死信队列信息,并且转发到其他mq*/@Scheduled(fixedDelay = 10 * 1000)public void subscribeMasterDeadQueue() throws IOException, TimeoutException {// 根据id 动态生成队列名称// 此处的queueIdList可以从第三方缓存查询得到,并且和sendDelayMsg接口保持同步刷新,此处先用本地缓存代替,id同步刷新机制不是重点,此处暂不讨论for (Integer id : QueueIdListConfig.QUEUE_ID_LIST) {String queueNameTemplate = DeclareQueueName.QUEUE_NAME_TEMPLATE.getQueueName();String deadQueueName = MessageFormat.format(queueNameTemplate, id) + DeclareQueueName.DEAD_QUEUE_NAME_SUFFIX.getQueueName();try (Connection connection = masterConnectionFactory.createConnection();Channel channel = connection.createChannel(false)){AMQP.Queue.DeclareOk queueDeclare = channel.queueDeclare(deadQueueName, true, false, false, null);if (queueDeclare.getConsumerCount() == 0) {channel.exchangeDeclare(DeclareQueueExchange.DEAD_EXCHANGE.getExchangeName(), BuiltinExchangeType.DIRECT);}channel.queueBind(deadQueueName, DeclareQueueExchange.DEAD_EXCHANGE.getExchangeName(), deadQueueName);channel.basicConsume(deadQueueName, false, new MasterDeadQueueConsumer(channel, slaveConnectionFactory));}}}}

创建定时任务,消费slave队列的消息

package com.wd.mq.quartz;import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.wd.config.DeclareQueueExchange;
import com.wd.config.DeclareQueueName;
import com.wd.config.QueueIdListConfig;
import com.wd.mq.consumer.SlaveQueueConsumer;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;import java.io.IOException;
import java.text.MessageFormat;
import java.util.concurrent.TimeoutException;@Configuration
@ConditionalOnBean(value = ConnectionFactory.class, name = "slaveConnectionFactory")
public class SlaveQueueSubscribeProcessor {private final ConnectionFactory slaveConnectionFactory;public SlaveQueueSubscribeProcessor(@Qualifier(value = "slaveConnectionFactory") ConnectionFactory slaveConnectionFactory) {this.slaveConnectionFactory = slaveConnectionFactory;}/*** 消费队列信息*/@Scheduled(fixedDelay = 10 * 1000)public void subscribeSlaveDeadQueue() throws IOException, TimeoutException {// 根据id 动态生成队列名称// 此处的queueIdList可以从第三方缓存查询得到,并且和sendDelayMsg接口保持同步刷新,此处先用本地缓存代替for (Integer id : QueueIdListConfig.QUEUE_ID_LIST) {String queueNameTemplate = DeclareQueueName.QUEUE_NAME_TEMPLATE.getQueueName();String queueName = MessageFormat.format(queueNameTemplate, id);try (Connection connection = slaveConnectionFactory.createConnection();Channel channel = connection.createChannel(false)){AMQP.Queue.DeclareOk queueDeclare = channel.queueDeclare(queueName, true, false, false, null);if (queueDeclare.getConsumerCount() == 0) {channel.basicConsume(queueName, false, new SlaveQueueConsumer(channel));}channel.exchangeDeclare(DeclareQueueExchange.EXCHANGE.getExchangeName(), BuiltinExchangeType.DIRECT);channel.queueBind(queueName, DeclareQueueExchange.EXCHANGE.getExchangeName(), queueName);}}}}

启动项目

请求接口发送消息 http://localhost:8080/sendDelayMsg

检查消息传递过程

先在master-mq延迟队列发现消息

再到master-mq死信队列中发现消息

再到slave-mq中发现消息

检查日志打印

发现SlaveQueueConsumer打印如下日志:

结论

消息传递流程如下,验证通过

相关文章:

springboot对接rabbitmq并且实现动态创建队列和消费

背景 1、对接多个节点上的MQ&#xff08;如master-MQ&#xff0c;slave-MQ&#xff09;&#xff0c;若读者需要自己模拟出两个MQ&#xff0c;可以部署多个VM然后参考 docker 安装rabbitmq_Steven-Russell的博客-CSDN博客 2、队列名称不是固定的&#xff0c;需要接受外部参数&…...

Spring的后处理器-BeanFactoryPostprocessor

目录 Spring后处理器 Bean工厂后处理器-BeanFactoryPostProcessor 修改beanDefinition对象 添加beanDefiniton对象 方法一 方法二 自定义Component Spring后处理器 Spring后处理器是Spring对外开放的重要拓展点&#xff08;让我们可以用添加自己的逻辑&#xff09;&…...

Flutter 必备知识点

Flutter 升级 确保在项目根目录下&#xff08;含有 pubspec.yaml 的文件夹&#xff09; 在命令行中输入命令&#xff1a; flutter channel输出&#xff1a; Flutter channels: * mastermainbetastable这个可以在 pubspec.yaml 中查看&#xff1a; 切换分支也很简单&#xf…...

什么是FMEA(失效模式和影响分析)?

失效模式和影响分析&#xff08;FMEA&#xff09;是一个在开发阶段&#xff0c;用于确定产品或流程可能的风险和失败点的有条理的过程。FMEA团队会研究失效模式&#xff0c;也就是产品或流程中可能出错的地方&#xff0c;以及这些失效可能带来的影响&#xff08;如风险、损害、…...

Redis面试题(三)

文章目录 前言一、怎么理解 Redis 事务&#xff1f;二、Redis 事务相关的命令有哪几个&#xff1f;三、Redis key 的过期时间和永久有效分别怎么设置&#xff1f;四、Redis 如何做内存优化&#xff1f;五、Redis 回收进程如何工作的&#xff1f;六、 加锁机制总结 前言 怎么理…...

Python错误处理指南:优雅应对异常情况

目录 一. 异常是什么&#xff1f;二. 使用 try 和 except三. 捕获多个异常四. 使用 else五. 使用 finally六. 自定义异常七.Python中常见异常处理类型八.Python中常见异常处理实例九.异常处理最佳实践十.结论 当编写Python代码时&#xff0c;错误处理是一个重要的方面&#xff…...

MySQL学习笔记12

MySQL 查询语句&#xff1a; 1、查询五子句&#xff1a;&#xff08;重点&#xff09; mysql> select */字段列表 from 数据表名称 where 子句 group by 子句 having 子句 order by 子句 limit 子句; 1&#xff09;where 子句&#xff1b;条件筛选。 2&#xff09;group…...

【owt】构建m79的owt-client-native:使用vs2017

家里电脑换成了台式机,拷贝代码发现了三年前的owt客户端mfc工程。 不用下载第三方库,试着构建下: owt-client-native 我这里有3年前的代码,思索了下还是用vs2017构建吧: 重新构建一下 选用x86 的 vs2017 vs的命令行控制台 cls可以清理屏幕 之前构建过vs2022的webrtc原版 …...

Cpp/Qt-day020918Qt

目录 完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两…...

Spring面试题10:Spring的XMLBeanFactory怎么使用

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring的XMLBeanFactory怎么使用 XmlBeanFactory是Spring框架中的一个实现类,它是BeanFactory接口的一个具体实现。XmlBeanFactory的主要作用是通…...

自定义数据类型

前言&#xff1a;小伙伴们又见面啦&#xff0c;今天这篇文章&#xff0c;我们来谈谈几种自定义数据类型。 目录 一.都有哪些自定义数据类型 二.结构体 结构体内存对齐 1.如何对齐 2.为什么要对齐 3.节省空间和提升效率的方法 &#xff08;1&#xff09;让占用空间小的成员…...

产品团队的需求验证和确认

需求核实过程是确保软件满足特定的规格要求&#xff0c;而验证则侧重于软件是否达到了最终用户的期望和需求。 如果你正在开发一种医疗产品&#xff0c;这种区别也可能在法规和标准中有所体现&#xff0c;例如&#xff1a; 820.30(f)&#xff1a;设计验证应确认设计的成果符合…...

【JVM】类加载的过程

文章目录 类的生命周期加载验证准备解析初始化简要概括 类的生命周期 一个类型从被加载到虚拟机内存中开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期将会经历加载 &#xff08;Loading&#xff09;、验证&#xff08;Verification&#xff09;、准备&#xf…...

Golang 结构化日志包 log/slog 详解(四):分组、上下文和属性值类型

上一篇文章讲解了 log/slog 包中的自定义日志属性字段和日志级别&#xff0c;本文讲解下分组、上下文和属性值类型 分组输出 slog 支持将字段放在组中并且可以给分组指定名称。如何展示分组的内容&#xff0c;取决于使用的 handler&#xff0c;例如 TextHandler 使用点号分隔…...

小白学Python:提取Word中的所有图片,只需要1行代码

#python# 大家好&#xff0c;这里是程序员晚枫&#xff0c;全网同名。 最近在小破站账号&#xff1a;Python自动化办公社区更新一套课程&#xff1a;给小白的《50讲Python自动化办公》 在课程群里&#xff0c;看到学员自己开发了一个功能&#xff1a;从word里提取图片。这个…...

pip修改位于用户目录下的缓存目录

默认 pip 缓存目录&#xff1a; Windows: C:\Users\${用户名}\AppData\Local\pip\cache Linux: ~/.cache/pip 一、修改方式 1.命令方式 pip config set global.cache-dir "D:\kwok\data\pip-cache" 2.配置文件方式 ① Windows&#xff1a; C:\Users\${用…...

更新、修改

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法: update 表名 列名该列新值, 列名该列新值, ... where 记录匹配条件; 说明&#xff1a;update 更新、修改 set 设置 …...

山西电力市场日前价格预测【2023-09-25】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-09-25&#xff09;山西电力市场全天平均日前电价为442.30元/MWh。其中&#xff0c;最高日前电价为720.46元/MWh&#xff0c;预计出现在19: 00。最低日前电价为276.06元/MWh&#xff0c;预计…...

从collections库的Counter类看items()方法和enumerate()方法

下面的代码是针对文件的词频统计&#xff0c;使用了collections库及其Counter类 import collections def count_word_frequency(text): words text.lower().split() word_counts collections.Counter(words) return word_counts def count_fileword_frequency(fi…...

2023-09-24 LeetCode每日一题(LRU 缓存)

2023-09-24每日一题 一、题目编号 146. LRU 缓存二、题目链接 点击跳转到题目位置 三、题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存i…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器

拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件&#xff1a; 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!

本文介绍了一种名为AnomalyAny的创新框架&#xff0c;该方法利用Stable Diffusion的强大生成能力&#xff0c;仅需单个正常样本和文本描述&#xff0c;即可生成逼真且多样化的异常样本&#xff0c;有效解决了视觉异常检测中异常样本稀缺的难题&#xff0c;为工业质检、医疗影像…...

通过MicroSip配置自己的freeswitch服务器进行调试记录

之前用docker安装的freeswitch的&#xff0c;启动是正常的&#xff0c; 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...

文件上传漏洞防御全攻略

要全面防范文件上传漏洞&#xff0c;需构建多层防御体系&#xff0c;结合技术验证、存储隔离与权限控制&#xff1a; &#x1f512; 一、基础防护层 前端校验&#xff08;仅辅助&#xff09; 通过JavaScript限制文件后缀名&#xff08;白名单&#xff09;和大小&#xff0c;提…...

STM32标准库-ADC数模转换器

文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”&#xff1a;输入模块&#xff08;GPIO、温度、V_REFINT&#xff09;1.4.2 信号 “调度站”&#xff1a;多路开关1.4.3 信号 “加工厂”&#xff1a;ADC 转换器&#xff08;规则组 注入…...