SpringBoot基于RabbitMQ实现消息延迟队列方案
知识小科普
在此之前,简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。
延时队列使用场景
在很多的业务场景中,延时队列可以实现很多功能,此类业务中,一般上是非实时的,需要延迟处理的,需要进行重试补偿的。
- 订单超时关闭:在支付场景中,一般上订单在创建后30分钟或1小时内未支付的,会自动取消订单。
- 短信或者邮件通知:在一些注册或者下单业务时,需要在1分钟或者特定时间后进行短信或者邮件发送相关资料的。本身此类业务于主业务是无关联性的,一般上的做法是进行异步发送。
- 重试场景:比如消息通知,在第一次通知出现异常时,会在隔几分钟之后进行再次重试发送。
RabbitMQ实现延时队列
本身在RabbitMQ中是未直接提供延时队列功能的,但可以使用 TTL(Time-To-Live,存活时间) 和 DLX(Dead-Letter-Exchange ,死信队列交换机)的特性实现延时队列的功能。
存活时间(Time-To-Live 简称 TTL)
RabbitMQ中可以对队列和消息分别设置TTL,TTL表明了一条消息可在队列中存活的最大时间。当某条消息被设置了TTL或者当某条消息进入了设置了TTL的队列时,这条消息会在TTL时间后死亡成为Dead Letter。如果既配置了消息的TTL,又配置了队列的TTL,那么较小的那个值会被取用。
死信交换(Dead Letter Exchanges 简称 DLX)
上个知识点也提到了,设置了 TTL 的消息或队列最终会成为 Dead Letter ,当消息在一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定此DLX的队列就是死信队列。
一个消息变成死信一般上是由于以下几种情况;
消息被拒绝
消息过期
队列达到了最大长度。
所以,通过 TTL 和 DLX 的特性可以模拟实现延时队列的功能。当队列中的消息超时成为死信后,会把消息死信重新发送到配置好的交换机中,然后分发到真实的消费队列。故简单来说,我们可以创建2个队列,一个队列用于发送消息,一个队列用于消息过期后的转发的目标队列。
SpringBoot集成RabbitMQ实现延时队列实战
以下使用 SpringBoot 集成 RabbitMQ 进行实战说明,在进行 http 消息通知时,若通知失败(地址不可用或者连接超时)时,将此消息转入延时队列中,待特定时间后进行重新发送。
0.引入pom依赖
<!-- rabbit --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!-- 简化http操作 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-http</artifactId><version>4.5.16</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-json</artifactId><version>4.5.16</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
1.编写rabbitmq配置文件(关键配置)RabbitConfig.java
/**
*
* @ClassName 类名:RabbitConfig
* @Description 功能说明:
* <p>
* TODO
*</p>
************************************************************************
* @date 创建日期:2019年7月17日
* @author 创建人:oKong
* @version 版本号:V1.0
*<p>
***************************修订记录*************************************
*
* 2019年7月17日 oKong 创建该类功能。
*
***********************************************************************
*</p>
*/
@Configuration
public class RabbitConfig {@AutowiredConnectionFactory connectionFactory;/*** 消费者线程数 设置大点 大概率是能通知到的*/@Value("${http.notify.concurrency:50}")int concurrency;/*** 延迟队列的消费者线程数 可设置小点*/@Value("${http.notify.delay.concurrency:20}")int delayConcurrency;@Beanpublic RabbitAdmin rabbitAdmin() {return new RabbitAdmin(connectionFactory);}@Beanpublic DirectExchange httpMessageNotifyDirectExchange(RabbitAdmin rabbitAdmin) {//durable 是否持久化//autoDelete 是否自动删除,即服务端或者客服端下线后 交换机自动删除DirectExchange directExchange = new DirectExchange(ApplicationConstant.HTTP_MESSAGE_EXCHANGE,true,false);directExchange.setAdminsThatShouldDeclare(rabbitAdmin);return directExchange;}//设置消息队列@Beanpublic Queue httpMessageStartQueue(RabbitAdmin rabbitAdmin) {/*创建接收队列,4个参数name - 队列名称durable - false,不进行持有化exclusive - true,独占性autoDelete - true,自动删除*/Queue queue = new Queue(ApplicationConstant.HTTP_MESSAGE_START_QUEUE_NAME, true, false, false);queue.setAdminsThatShouldDeclare(rabbitAdmin);return queue;}//队列绑定交换机@Beanpublic Binding bindingStartQuene(RabbitAdmin rabbitAdmin,DirectExchange httpMessageNotifyDirectExchange, Queue httpMessageStartQueue) {Binding binding = BindingBuilder.bind(httpMessageStartQueue).to(httpMessageNotifyDirectExchange).with(ApplicationConstant.HTTP_MESSAGE_START_RK);binding.setAdminsThatShouldDeclare(rabbitAdmin);return binding;}@Beanpublic Queue httpMessageOneQueue(RabbitAdmin rabbitAdmin) {Queue queue = new Queue(ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME, true, false, false);queue.setAdminsThatShouldDeclare(rabbitAdmin);return queue;}@Beanpublic Binding bindingOneQuene(RabbitAdmin rabbitAdmin,DirectExchange httpMessageNotifyDirectExchange, Queue httpMessageOneQueue) {Binding binding = BindingBuilder.bind(httpMessageOneQueue).to(httpMessageNotifyDirectExchange).with(ApplicationConstant.HTTP_MESSAGE_ONE_RK);binding.setAdminsThatShouldDeclare(rabbitAdmin);return binding;}//-------------设置延迟队列--开始--------------------@Beanpublic Queue httpDelayOneQueue() {//name - 队列名称//durable - true//exclusive - false//autoDelete - falsereturn QueueBuilder.durable("http.message.dlx.one")//以下是重点:当变成死信队列时,会转发至 路由为x-dead-letter-exchange及x-dead-letter-routing-key的队列中.withArgument("x-dead-letter-exchange", ApplicationConstant.HTTP_MESSAGE_EXCHANGE).withArgument("x-dead-letter-routing-key", ApplicationConstant.HTTP_MESSAGE_ONE_RK).withArgument("x-message-ttl", 1*60*1000)//1分钟 过期时间(单位:毫秒),当过期后 会变成死信队列,之后进行转发.build();}//绑定到交换机上@Beanpublic Binding bindingDelayOneQuene(RabbitAdmin rabbitAdmin, DirectExchange httpMessageNotifyDirectExchange, Queue httpDelayOneQueue) {Binding binding = BindingBuilder.bind(httpDelayOneQueue).to(httpMessageNotifyDirectExchange).with("delay.one");binding.setAdminsThatShouldDeclare(rabbitAdmin);return binding;}//-------------设置延迟队列--结束--------------------//建议将正常的队列和延迟处理的队列分开//设置监听容器@Bean("notifyListenerContainer")public SimpleRabbitListenerContainerFactory httpNotifyListenerContainer() {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 手动ackfactory.setConnectionFactory(connectionFactory);factory.setPrefetchCount(1);factory.setConcurrentConsumers(concurrency);return factory;}// 设置监听容器@Bean("delayNotifyListenerContainer")public SimpleRabbitListenerContainerFactory httpDelayNotifyListenerContainer() {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 手动ackfactory.setConnectionFactory(connectionFactory);factory.setPrefetchCount(1);factory.setConcurrentConsumers(delayConcurrency);return factory;}
}
ApplicationConstant.java
public class ApplicationConstant {/*** 发送http通知的 exchange 队列*/public static final String HTTP_MESSAGE_EXCHANGE = "http.message.exchange";/*** 配置消息队列和路由key值*/public static final String HTTP_MESSAGE_START_QUEUE_NAME = "http.message.start";public static final String HTTP_MESSAGE_START_RK = "rk.start";public static final String HTTP_MESSAGE_ONE_QUEUE_NAME = "http.message.one";public static final String HTTP_MESSAGE_ONE_RK = "rk.one";/*** 通知队列对应的延迟队列关系,即过期队列之后发送到下一个的队列信息,可以根据实际情况添加,当然也可以根据一定规则自动生成*/public static final Map<String,String> delayRefMap = new HashMap<String, String>() {/*** */private static final long serialVersionUID = -779823216035682493L;{put(HTTP_MESSAGE_START_QUEUE_NAME, "delay.one");}};
}
简单来说,就是创建一个正常消息发送队列,用于接收http消息请求的参数,同时进行http请求。同时,创建一个延时队列,设置其 x-dead-letter-exchange 、x-dead-letter-routing-key 和
x-message-ttl 值,将其转发到正常的队列中。使用一个map对象维护一个关系,当正常消息异常时,需要发送的延时队列的队列名称,当然时间场景汇总,根据需要可以进行动态配置或者根据一定规则进行动态映射。
2.创建监听类,用于消息的消费操作,此处使用@RabbitListener来消费消息(当然也可以使用SimpleMessageListenerContainer进行消息配置的),创建了一个正常消息监听和延时队列监听,由于一般上异常通知是低概率事件,可根据不同的监听容器进行差异化配置。
/**
*
* @ClassName 类名:HttpMessagerLister
* @Description 功能说明:http通知消费监听接口
* <p>
* TODO
*</p>
************************************************************************
* @date 创建日期:2019年7月17日
* @author 创建人:oKong
* @version 版本号:V1.0
*<p>
***************************修订记录*************************************
*
* 2019年7月17日 oKong 创建该类功能。
*
***********************************************************************
*</p>
*/
@Component
@Slf4j
public class HttpMessagerLister {@AutowiredHttpMessagerService messagerService;@RabbitListener(id = "httpMessageNotifyConsumer", queues = {ApplicationConstant.HTTP_MESSAGE_START_QUEUE_NAME}, containerFactory = "notifyListenerContainer")public void httpMessageNotifyConsumer(Message message, Channel channel) throws Exception {doHandler(message, channel);}@RabbitListener(id= "httpDelayMessageNotifyConsumer", queues = {ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME,}, containerFactory = "delayNotifyListenerContainer")public void httpDelayMessageNotifyConsumer(Message message, Channel channel) throws Exception {doHandler(message, channel);}private void doHandler(Message message, Channel channel) throws Exception {String body = new String(message.getBody(),"utf-8");String queue = message.getMessageProperties().getConsumerQueue();log.info("接收到通知请求:{},队列名:{}",body, queue);//消息对象转换try {HttpEntity httpNotifyDto = JSONUtil.toBean(body, HttpEntity.class);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//发送通知messagerService.notify(queue, httpNotifyDto);} catch(Exception e) {log.error(e.getMessage());//ackchannel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}}
}
HttpMessagerService.java :消息真正处理的类,此类是关键,这里未进行日志记录,真实场景中,强烈建议进行消息通知的日志存储,防止日后信息的查看,同时也能通过发送状态,在重试次数都失败后,进行定时再次发送功能,同时也有据可查。
@Component
@Slf4j
public class HttpMessagerService {@AutowiredAmqpTemplate mqTemplate; public void notify(String queue,HttpEntity httpEntity) {//发起请求log.info("开始发起http请求:{}", httpEntity);try {switch(httpEntity.getMethod().toLowerCase()) {case "POST":HttpUtil.post(httpEntity.getUrl(), httpEntity.getParams());break;case "GET":default:HttpUtil.get(httpEntity.getUrl(), httpEntity.getParams());}} catch (Exception e) {//发生异常,放入延迟队列中String nextRk = ApplicationConstant.delayRefMap.get(queue);if(ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME.equals(queue)) {//若已经是最后一个延迟队列的消息队列了,则后续可直接放入数据库中 待后续定时策略进行再次发送log.warn("http通知已经通知N次失败,进入定时进行发起通知,url={}", httpEntity.getUrl());} else {log.warn("http重新发送通知:{}, 通知队列rk为:{}, 原队列:{}", httpEntity.getUrl(), nextRk, queue);mqTemplate.convertAndSend(ApplicationConstant.HTTP_MESSAGE_EXCHANGE, nextRk, cn.hutool.json.JSONUtil.toJsonStr(httpEntity));}}}
}
3.创建控制层服务(真实场景中,如SpringCloud微服务中,一般上是创建个api接口,供其他服务进行调用)
@Slf4j
@RestController
@Api(tags = "http测试接口")
public class HttpDemoController {@AutowiredAmqpTemplate mqTemplate;@PostMapping("/send")@ApiOperation(value="send",notes = "发送http测试")public String sendHttp(@RequestBody HttpEntity httpEntity) {//发送http请求log.info("开始发起http请求,发布异步消息:{}", httpEntity);mqTemplate.convertAndSend(ApplicationConstant.HTTP_MESSAGE_EXCHANGE, ApplicationConstant.HTTP_MESSAGE_START_RK, cn.hutool.json.JSONUtil.toJsonStr(httpEntity));return "发送成功:url=" + httpEntity.getUrl(); }
}
4.配置文件添加RabbitMQ相关配置信息
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/# 通知-消费者线程数 设置大点 大概率是能通知到的
http.notify.concurrency=150
# 延迟队列的消费者线程数 可设置小点
http.notify.delay.concurrency=10
5.编写启动类。
@SpringBootApplication
@Slf4j
public class DelayQueueApplication {public static void main(String[] args) throws Exception {SpringApplication.run(DelayQueueApplication.class, args);log.info("spring-boot-rabbitmq-delay-queue-chapter38服务启动!");}
}
6.启动服务。使用swagger进行简单调用测试。
- 正常通知:

2019-07-20 23:52:23.792 INFO 65216 --- [nio-8080-exec-1] c.l.l.s.c.controller.HttpDemoController : 开始发起http请求,发布异步消息:HttpEntity(url=www.baidu.com, params={a=1}, method=get)
2019-07-20 23:52:23.794 INFO 65216 --- [TaskExecutor-97] c.l.l.s.chapter38.mq.HttpMessagerLister : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com"},队列名:http.message.start
2019-07-20 23:52:23.794 INFO 65216 --- [TaskExecutor-97] c.l.l.s.c.service.HttpMessagerService : 开始发起http请求:HttpEntity(url=www.baidu.com, params={a=1}, method=get)
- 异常通知:访问一个不存在的地址

2019-07-20 23:53:14.699 INFO 65216 --- [nio-8080-exec-4] c.l.l.s.c.controller.HttpDemoController : 开始发起http请求,发布异步消息:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:53:14.705 INFO 65216 --- [TaskExecutor-84] c.l.l.s.chapter38.mq.HttpMessagerLister : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com1"},队列名:http.message.start
2019-07-20 23:53:14.705 INFO 65216 --- [TaskExecutor-84] c.l.l.s.c.service.HttpMessagerService : 开始发起http请求:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:53:14.706 WARN 65216 --- [TaskExecutor-84] c.l.l.s.c.service.HttpMessagerService : http重新发送通知:www.baidu.com1, 通知队列rk为:delay.one, 原队列:http.message.start
在 RabbitMQ 后台中,可以看见 http.message.dlx.one 队列中存在这需要延时处理的消息,在一分钟后会转发至 http.message.one 队列中。

在一分钟后,可以看见消息本再次消费了。
2019-07-20 23:54:14.722 INFO 65216 --- [TaskExecutor-16] c.l.l.s.chapter38.mq.HttpMessagerLister : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com1"},队列名:http.message.one
2019-07-20 23:54:14.723 INFO 65216 --- [TaskExecutor-16] c.l.l.s.c.service.HttpMessagerService : 开始发起http请求:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:54:14.723 WARN 65216 --- [TaskExecutor-16] c.l.l.s.c.service.HttpMessagerService : http通知已经通知N次失败,进入定时进行发起通知,url=www.baidu.com1
一些最佳实践
在正式场景中,一般上补偿或者重试机制大概率是不会发送的,倘若发生时,一般上是第三方业务系统出现了问题,故一般上在进行补充时,应该在非高峰期进行操作,故应该对延时监听器,应该在高峰期时停止消费,在非高峰期时进行消费。同时,还可以根据不同的通知类型,放入不一样的延时队列中,保障业务的正常。这里简单说明下,动态停止或者启动演示监听器的方式。一般上是使用RabbitListenerEndpointRegistry 对象获取延时监听器,之后进行动态停止或者启用。可设置 @RabbitListener 的id属性,直接进行获取,当然也可以直接获取所有的监听器,进行自定义判断了。
@AutowiredRabbitListenerEndpointRegistry registry;@GetMapping("/set")@ApiOperation(value = "set", notes = "设置消息监听器的状态")public String setSimpleMessageListenerContainer(String status) {if("1".equals(status)) {registry.getListenerContainer("httpDelayMessageNotifyConsumer").start();} else {registry.getListenerContainer("httpDelayMessageNotifyConsumer").stop();}return status;}
这里,只是简单进行演示说明,在真实场景下,可以使用定时器,判断当前是否为高峰期,进而进行动态设置监听器的状态。
参考资料
- https://www.rabbitmq.com/admin-guide.html
- https://www.rabbitmq.com/ttl.html
相关文章:
SpringBoot基于RabbitMQ实现消息延迟队列方案
知识小科普 在此之前,简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。 延时队列使用场景 在很多的业务场景中,延时队列可以实现很多功能,此类业务中,一般上是非实时的,需要延迟处理的&a…...
Go语言使用标准库时常见错误
Go的标准库是一组增加和拓展语言的核心包。然而,很容易误用标准库,或者我们对其行为理解有限,导致产生了bug或不应该在生产级应用程序中某些功能。 1. 提供错误的持续时间 标准库提供了获取 time.Duration 的常用函数和方法,但由于 time.Duration 是 int64 的自定义类型,…...
UE5不打包启用像素流 ubuntu22.04
首先查找引擎中像素流的位置: zkzk-ubuntu2023:/media/zk/Data/Linux_Unreal_Engine_5.3.2$ sudo find ./ -name get_ps_servers.sh [sudo] zk 的密码: ./Engine/Plugins/Media/PixelStreaming/Resources/WebServers/get_ps_servers.sh然后在指定路径中…...
Redis 常用数据类型常用命令和应用场景
首先先混个眼熟 Redis 中的 8 种常用数据类型: 5 种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合࿰…...
ins视频批量下载,instagram批量爬取视频信息
简介 Instagram 是目前最热门的社交媒体平台之一,拥有大量优质的视频内容。但是要逐一下载这些视频往往非常耗时。在这篇文章中,我们将介绍如何使用 Python 编写一个脚本,来实现 Instagram 视频的批量下载和信息爬取。 我们使用selenium获取目标用户的 HTML 源代码,并将其保存…...
Canvas图形编辑器-数据结构与History(undo/redo)
Canvas图形编辑器-数据结构与History(undo/redo) 这是作为 社区老给我推Canvas,于是我也学习Canvas做了个简历编辑器 的后续内容,主要是介绍了对数据结构的设计以及History能力的实现。 在线编辑: https://windrunnermax.github.io/CanvasEditor开源地…...
阿里云Centos7下编译glibc
编译glibc 原来glibc版本 编译前需要的环境: CentOS7 gcc 8.3.0 gdb 8.3.0 make 4.0 binutils 2.39 (ld -v) python 3.6.8 其他看INSTALL, 但有些版本也不易太高 wget https://mirrors.aliyun.com/gnu/glibc/glibc-2.37.tar.gz tar -zxf glibc-2.37.tar.gz cd glibc-2.37/ …...
UE5数字孪生系列笔记(四)
场景的切换 创建一个按钮的用户界面UMG 创建一个Actor,然后将此按钮UMG添加到组件Actor中 调节几个全屏的背景 运行结果 目标点切换功能制作 设置角色到这个按钮的位置效果 按钮被点击就进行跳转 多个地点的切换与旋转 将之前的目标点切换逻辑替换成旋转的逻…...
品牌故事化:Kompas.ai如何塑造深刻的品牌形象
在这个信息爆炸的时代,品牌故事化已经成为企业塑造独特形象、与消费者建立情感联系的重要手段。一个引人入胜的品牌故事不仅能够吸引消费者的注意力,还能够在消费者心中留下持久的印象,建立起强烈的情感连接。本文将深入探讨品牌故事化对于构…...
5g和2.4g频段有什么区别
运行的频段不同 2.4G和5G频段的主要区别在于它们运行的频段不同,2.4G频段运行在2.4GHz的频段上,而5G频段(这里指的是5GHz频段)运行在5GHz的频段上。12 这导致了两者在传输速度、覆盖范围、抗干扰能力等方面的明显差异。以下是详…...
交通管理在线服务系统|基于Springboot的交通管理系统设计与实现(源码+数据库+文档)
交通管理在线服务系统目录 目录 基于Springboot的交通管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、驾驶证业务管理 3、机动车业务管理 4、机动车业务类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计…...
konva.js 工具类
konva.js 工具类 class KonvaCanvas {/*** 初始化画布* param {String} domId 容器dom id*/constructor(domId) {this.layer null;this.stage null;this.scale 1;this.init(domId);}/*** 聚焦到指定元素* param {String} elementId 元素dom id*/focusOn(elementId) {if (!t…...
php未能在vscode识别?
在设置里搜php,找到settings.json,设置你的安装路径即可。 成功...
解读MongoDB官方文档获取mongo7.0版本的安装步骤与基本使用
mongo式一款NOSQL数据库,用于存储非结构化数据,mongo是一种用于存储json的数据数据,可以通过mongo提供的命令解析json获取想要的值。 数据模型 了解关系数据库会很熟悉database,table,row,column的概念,分别是数据库,…...
【数据结构|C语言版】顺序表
前言1. 初步认识数据结构2. 线性表3. 顺序表3.1 顺序表的概念3.1 顺序表的分类3.2 动态顺序表的实现 结语 前言 各位小伙伴大家好!小编来给大家讲解一下数据结构中顺序表的相关知识。 1. 初步认识数据结构 【概念】数据结构是计算机存储、组织数据的⽅式。 数据…...
Unity类银河恶魔城学习记录12-17 p139 In game UI源代码
Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili UI.cs using UnityEngine;public class UI : MonoBehaviour {[SerializeFie…...
MongoDB学习【一】MongoDB简介和部署
MongoDB简介 MongoDB是一种开源的、面向文档的、分布式的NoSQL数据库系统,由C语言编写而成。它的设计目标是为了适应现代Web应用和大数据处理场景的需求,提供高可用性、横向扩展能力和灵活的数据模型。 主要特点: 文档模型: Mon…...
html 引入vue Element ui 的方式
第一种:使用CDN的方式引入 <!--引入 element-ui 的样式,--> <link rel"stylesheet" href"https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 必须先引入vue, 后使用element-ui --> <…...
曾经备受追捧的海景房,为何如今却没人要了?
独家首发 ------------ 全国多地的海景房如威海乳山、惠州大亚湾、北海银滩等多地的海景房如今大跌也难以卖出,与当初各地对海景房的追捧形成了鲜明对比,为何这些海景房变成如此样子,在于现实与宣传存在着很大的区别。 曾几何时面朝大海鸟语花…...
[docker] 镜像部分补充
[docker] 镜像部分补充 这里补充一下比较少用的,关于镜像的内容 检查镜像 ❯ docker images REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> ca61c1748170 2 hours ago 1.11GB node latest 5212d…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
