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

RabbitMQ实现延迟消息发送——实战篇

在项目中,我们经常需要使用消息队列来实现延迟任务,本篇文章就向各位介绍使用RabbitMQ如何实现延迟消息发送,由于是实战篇,所以不会讲太多理论的知识,还不太理解的可以先看看MQ的延迟消息的一个实现原理再来看这篇文章跟着练手哦~

需求背景

我这儿的一个需求背景大概是干部添加完活动后,由管理员进行审批,审批通过后,该活动id连同设置的过期时间会被放入消息队列中,等到活动结束时间到的时候,自动将活动的状态设置为已完成,这里华丽一个活动图,各位参考一下。

ce9bd03466514d6294a1a1de81f7772d.png

需求了解完之后我们就可以开始的写代码啦~(手动微笑)

相关知识点拓展

这里还是简单提一下MQ实现延迟队列的一个方法,一种是用插件,还有一种是使用死信队列,当然本文我们使用的就是通过死信队列来实现的。

当我们的一个正常消息因为设置了过期时间或者被消费者拒绝消费的时候,这条消息就会被放入死信队列中,然后死信队列再进行消费。

然后啰嗦一下,说一下MQ的交换机类型,以及死信交换机一般选用哪种:

1. Direct Exchange(直连交换机)

  • 特点
    • 根据消息的 Routing Key 精确匹配队列的 Binding Key
    • 完全匹配时,消息才会被路由到对应的队列。
  • 适用场景
    • 点对点消息传递,消息需要精确路由到特定队列。
  • 示例
    • 消息的 Routing Key 为 order.created,队列的 Binding Key 也为 order.created,则消息会被路由到该队列。

2. Fanout Exchange(扇出交换机)

  • 特点
    • 将消息广播到所有绑定到该交换机的队列,忽略 Routing Key。
  • 适用场景
    • 广播消息,消息需要发送到多个队列。
  • 示例
    • 消息发送到 Fanout Exchange,所有绑定到该交换机的队列都会收到消息。

3. Topic Exchange(主题交换机)

  • 特点
    • 根据消息的 Routing Key 和队列的 Binding Key 进行模式匹配。
    • Binding Key 支持通配符:
      • *:匹配一个单词。
      • #:匹配零个或多个单词。
  • 适用场景
    • 消息需要根据模式路由到多个队列。
  • 示例
    • 消息的 Routing Key 为 order.created.us,队列的 Binding Key 为 order.created.*,则消息会被路由到该队列。

4. Headers Exchange(头交换机)

  • 特点
    • 根据消息的 Headers(键值对)匹配队列的 Binding Arguments。
    • 忽略 Routing Key。
  • 适用场景
    • 消息需要根据复杂的条件路由到队列。
  • 示例
    • 消息的 Headers 包含 type=order 和 region=us,队列的 Binding Arguments 要求 x-match=all 且 type=order,则消息会被路由到该队列。

5. Default Exchange(默认交换机)

  • 特点
    • RabbitMQ 默认创建的交换机,类型为 Direct Exchange。
    • 每个队列都会自动绑定到默认交换机,Binding Key 为队列名称。
  • 适用场景
    • 默认情况下,消息可以直接发送到队列。

死信交换机适合使用哪种类型?

死信交换机(DLX, Dead Letter Exchange)的类型选择取决于你的业务需求。以下是常见的选择:

1. Direct Exchange

  • 适用场景
    • 死信消息需要精确路由到特定的死信队列。
  • 示例
    • 将死信消息路由到 dlx-queue,用于统一处理所有死信消息。

2. Topic Exchange

  • 适用场景
    • 死信消息需要根据不同的 Routing Key 路由到不同的死信队列。
  • 示例
    • 将死信消息根据业务类型(如 order.deadpayment.dead)路由到不同的死信队列。

3. Fanout Exchange

  • 适用场景
    • 死信消息需要广播到多个死信队列。
  • 示例
    • 将死信消息同时发送到日志队列和报警队列。

推荐选择

  • 大多数情况下,死信交换机使用 Direct Exchange,因为死信消息通常需要精确路由到一个死信队列,用于统一处理。
  • 如果死信消息需要根据不同的条件路由到多个队列,可以使用 Topic Exchange

代码部分

首先,我们需要定义一个死信交换机和死信队列,用来接收来自普通队列的消息。

//    创建死信交换机,处理延迟消息通知@Bean("dead_letter_exchange")public DirectExchange delayExchange(){return new DirectExchange("dead_letter_exchange",true,false);}
//    创建死信队列public Queue deadLetterQueue(){Queue queue = new Queue("dead_letter_queue", true);rabbitAdmin.declareQueue(queue);log.info("死信队列声明成功:" + queue.getName());return queue;    }

然后,我们需要配置一个普通的消息队列和一个普通的交换机,这个消息队列需要设置对应的死信交换机和死信路由,同时我们这个普通队列需要接收一个过期时间,保证一到过期时间消息就会被发送到死信队列当中。

//    创建一个普通队列,接受一个过期时间,出列活动结束后,发送到死信队列public Queue normalQueue(Long expireTime){Map<String,Object> args = new HashMap<>();if (expireTime != null && expireTime > 0) {  // 确保 TTL 是正数args.put("x-message-ttl", expireTime);}// 设置死信交换机args.put("x-dead-letter-exchange",deadLetterExchange);// 设置死信路由键args.put("x-dead-letter-routing-key","dead_letter_routing_key");Queue queue = new Queue("normal_queue", true, false, false, args);log.info("普通队列声明成功:" + queue.getName());return queue;    }
//    创建一个普通交换机,处理活动结束自动设置活动状态为结束@Bean("activity_end_exchange")public DirectExchange activityEndExchange(){return new DirectExchange("activity_end_exchange");}

然后我们需要分别将死信交换机和死信队列,普通交换机和普通队列分别进行绑定。

//    将死信队列和死信交换机进行绑定public void bindDeadLetterRouting(){Queue queue=queueDeclareConfig.deadLetterQueue();Binding binding = BindingBuilder.bind(queue).to(deadLetterExchange).with("dead_letter_routing_key");rabbitAdmin.declareBinding(binding);log.info("死信队列绑定成功,死信队列名称----》" + queue.getName() + ",死信交换机名称----》" + deadLetterExchange.getName());}//    绑定活动结束交换机和普通队列public void bindActivityEndRouting(Long expireTime) {Queue queue = queueDeclareConfig.normalQueue(expireTime);Binding binding = BindingBuilder.bind(queue).to(activityEndExchange).with("activity_end_routing_key");rabbitAdmin.declareBinding(binding);}

当然,我们还需要配置生产者来发送消息到交换机里面

//活动结束后,发送消息到死信队列,自动设置活动结束状态public void sendActivityEndMessage(Long expireTime, Integer activityId) {rabbitMQBindRoutingConfig.bindDeadLetterRouting();rabbitMQBindRoutingConfig.bindActivityEndRouting(expireTime);try {// 将消息发送到普通队列,等待消息过期发送到死信交换机rabbitTemplate.convertAndSend("activity_end_exchange", "activity_end_routing_key", activityId, msg -> {msg.getMessageProperties().setExpiration(expireTime.toString());return msg;});} catch (Exception e) {log.error("发送消息失败------->" + activityId);throw new RuntimeException("发送消息失败---->" + activityId);}}

这里生产者的代码可以根据你的业务逻辑具体进行更改~

消费者逻辑也需要进行编写一下

//    使用MQ延迟队列,活动结束,修改活动状态@RabbitListener(queues = "dead_letter_queue")public void updatePlaceOccupyStatus(Message message, Channel channel){try {String messageBody = new String(message.getBody(), StandardCharsets.UTF_8);Integer activityId = Integer.parseInt(messageBody);ActivityInfo activityInfo = baseMapper.selectById(activityId);LambdaUpdateWrapper<ActivityInfo> wrapper = new LambdaUpdateWrapper<>();wrapper.eq(ActivityInfo::getActivityId,activityId).set(ActivityInfo::getProgress,StatusConstant.FINISH);if(baseMapper.update(activityInfo,wrapper)>0){channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}} catch (Exception e) {log.error("处理消息时发生错误:" + e.getMessage());try {channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);} catch (IOException ioException) {ioException.printStackTrace();}}

消费者这边需要注意的是如果你选择的提交类型不是自动提交的话,在处理完消息之后需要手动ack一下消息,不然消费的消息不会被认为已经消费,从而导致消息积压,也会在之后的消费中重复进行消费,因此你需要告诉生产者这条消息已经被消费了。

当然,如果在消费的过程中出现了什么问题,可以设置以下这行代码:

419e93610db14c019fcfb49ad0d23703.png

basicNack方法接收三个参数:
deliveryTag: 消息的标识符。
multiple: 是否对多个消息进行否定确认。
requeue: 是否将消息重新放入队列。 

可以根据你的需求进行设定~

然后的然后,我们需要再application.yml当中进行配置相关信息:

rabbitmq:host: localhostport: 5672username: guestpassword: guest
#    确认消息发送到交换机上publisher-confirm-type: correlated#    消息发送到队列确认,失败回调publisher-returns: truelistener:direct:acknowledge-mode: manualretry:enabled: true
#          重试的时间间隔为1sinitial-interval: 1000ms
#          最大重试3次max-attempts: 3
#          最大的重试时间间隔为2smax-interval: 2000ms
#          每次重试时间间隔为1s,每次重试时间间隔倍数multiplier: 1.0#重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)default-requeue-rejected: falsesimple:default-requeue-rejected: falseacknowledge-mode: manual
#        最小消费者数量concurrency: 1
#        最大消费者数量max-concurrency: 10retry:enabled: trueinitial-interval: 1000msmax-attempts: 3max-interval: 2000msmultiplier: 1.0

上面给出了一个比较全的配置,你可以根据你的需求进行选择,但是需要注意的是default-requeue-rejected: false这一行配置一定要先配置,不然你的消息在普通队列中过期了,是不会发送到死信队列当中进行消费的~

到这儿,基本上所有的代码都写的差不多了,当然我们还需要再rabbitmq控制平台上分别建一个普通交换机和一个死信交换机,一个普通队列和一个私信队列,然后分别绑定就可以了。

注意的是,普通交换机也需要在平台上配置一次死信队列和死信路由:

5f1c7325f13a4910bd2d28e8b62a7f60.png

1d9202ed97554cc4ab1601d6f839b0ef.png

到这儿,如果没有什么问题的话基本上已经可以直接运行了,所以我的这篇文章到这儿基本上也已经结束了,如果你有什么问题,可以评论区留言,我们相互学习~

 

相关文章:

RabbitMQ实现延迟消息发送——实战篇

在项目中&#xff0c;我们经常需要使用消息队列来实现延迟任务&#xff0c;本篇文章就向各位介绍使用RabbitMQ如何实现延迟消息发送&#xff0c;由于是实战篇&#xff0c;所以不会讲太多理论的知识&#xff0c;还不太理解的可以先看看MQ的延迟消息的一个实现原理再来看这篇文章…...

Oracle 拉链式merge sort join 原理

Oracle 拉链式Merge Sort Join 的原理&#xff0c;我用一个生活中的比喻来解释。 --- 比喻场景&#xff1a;匹配快递包裹和收件人 1. 快递包裹清单 想象我们有一个快递公司送货的包裹清单&#xff0c;清单按照收件人的邮编&#xff08;ZIP Code&#xff09;排序&#xff1a; …...

QModbusTCPClient占用内存持续增长

最近使用QModbusTCPClient通信&#xff0c;需要频繁发送读写请求&#xff0c;发现软件占用内存一直在增减&#xff0c;经过不断咨询和尝试&#xff0c;终于解决了。 1.方案一&#xff08;失败&#xff09; 最开始以为是访问太频繁&#xff0c;导致创建reply的对象比delete re…...

代码中使用 Iterable<T> 作为方法参数的解释

/*** 根据课程 id 集合查询课程简单信息* param ids id 集合* return 课程简单信息的列表*/ GetMapping("/courses/simpleInfo/list") List<CourseSimpleInfoDTO> getSimpleInfoList(RequestParam("ids") Iterable<Long> ids); 一、代码解释&…...

Oracle数据库传统审计怎么用

Oracle数据库传统审计怎么用 审计功能开启与关闭By Session还是By AccessWhenever Successful数据库语句审计数据库对象审计查看审计策略和记录Oracle数据库审计功能分为传统审计(Traditional Auditing)和统一审计(Unified Auditing)。统一审计是从Oracle 12c版本开始引入的…...

leetcode-买卖股票问题

309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 动态规划解题思路&#xff1a; 1、暴力递归&#xff08;难点如何定义递归函数&#xff09; 2、记忆化搜索-傻缓存法&#xff08;根据暴力递归可变参数确定缓存数组维度&#xff09; 3、严格表结构依…...

MYSQL学习笔记(三):分组、排序、分页查询

前言&#xff1a; 学习和使用数据库可以说是程序员必须具备能力&#xff0c;这里将更新关于MYSQL的使用讲解&#xff0c;大概应该会更新30篇&#xff0c;涵盖入门、进阶、高级(一些原理分析);这一篇是讲解分组、排序、分页查询&#xff0c;并且结合案例进行讲解&#xff1b;虽…...

上位机工作感想-2024年工作总结和来年计划

随着工作年限的增增长&#xff0c;发现自己越来越不喜欢在博客里面写一些掺杂自己感想的东西了&#xff0c;或许是逐渐被工作逼得“成熟”了吧。2024年&#xff0c;学到了很多东西&#xff0c;做了很多项目&#xff0c;也帮别人解决了很多问题&#xff0c;唯独没有涨工资。来这…...

【视觉惯性SLAM:十六、 ORB-SLAM3 中的多地图系统】

16.1 多地图的基本概念 多地图系统是机器人和计算机视觉领域中的一种关键技术&#xff0c;尤其在 SLAM 系统中具有重要意义。单一地图通常用于表示机器人或相机在环境中的位置和构建的空间结构&#xff0c;但单一地图在以下情况下可能无法满足需求&#xff1a; 大规模场景建图…...

【C++笔记】红黑树封装map和set深度剖析

【C笔记】红黑树封装map和set深度剖析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】红黑树封装map和set深度剖析前言一. 源码及框架分析1.1 源码框架分析 二. 模拟实现map和set2.1封装map和set 三.迭代器3.1思路…...

4.若依 BaseController

若依的BaseController是其他所有Controller的基类&#xff0c;一起来看下BaseController定义了什么 1. 定义请求返回内容的格式 code/msg/data 返回数据格式不是必须是AjaxResult&#xff0c;开发者可以自定义返回格式&#xff0c;注意与前端取值方式一致即可。 2. 获取调用…...

vue项目配置多语言

本文详细介绍如何在 Vue 项目中集成 vue-i18n 和 Element-UI &#xff0c;实现多语言切换&#xff1b;首先通过 npm 安装 vue-i18n 和相关语言包&#xff0c;接着在配置文件中设置中文和英文的语言信息&#xff1b;最后在 main.js 中导入并挂载多语言实例&#xff0c;实现切换地…...

数据可视化大屏设计与实现

本文将带你一步步了解如何使用 ECharts 实现一个数据可视化大屏&#xff0c;并且如何动态加载天气数据展示。通过整合 HTML、CSS、JavaScript 以及后端接口请求&#xff0c;我们可以构建一个响应式的数据可视化页面。 1. 页面结构介绍 在此例中&#xff0c;整个页面分为几个主…...

PDF文件提取开源工具调研总结

概述 PDF是一种日常工作中广泛使用的跨平台文档格式&#xff0c;常常包含丰富的内容&#xff1a;包括文本、图表、表格、公式、图像。在现代信息处理工作流中发挥了重要的作用&#xff0c;尤其是RAG项目中&#xff0c;通过将非结构化数据转化为结构化和可访问的信息&#xff0…...

多监控m3u8视频流,怎么获取每个监控的封面图(纯前端)

文章目录 1.背景2.问题分析3.解决方案3.1解决思路3.2解决过程3.2.1 封装播放组件3.2.2 隐形的视频div3.2.3 截取封面图 3.3 结束 1.背景 有这样一个需求&#xff1a; 给你一个监控列表&#xff0c;每页展示多个监控&#xff08;至少12个&#xff0c;m3u8格式&#xff09;&…...

【机器学习实战入门项目】使用深度学习创建您自己的表情符号

深度学习项目入门——让你更接近数据科学的梦想 表情符号或头像是表示非语言暗示的方式。这些暗示已成为在线聊天、产品评论、品牌情感等的重要组成部分。这也促使数据科学领域越来越多的研究致力于表情驱动的故事讲述。 随着计算机视觉和深度学习的进步&#xff0c;现在可以…...

技术洞察:C++在后端开发中的前沿趋势与社会影响

文章目录 引言C在后端开发中的前沿趋势1. 高性能计算的需求2. 微服务架构的兴起3. 跨平台开发的便利性 跨领域技术融合与创新实践1. C与人工智能的结合2. C与区块链技术的融合 C对社会与人文的影响1. 提升生产力与创新能力2. 促进技术教育与人才培养3. 技术与人文的深度融合 结…...

【人工智能 | 大数据】基于人工智能的大数据分析方法

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘&#xff0c;以提取有价值的信息和洞察。它结合了大数据技术、人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&a…...

数字经济时代下的创新探索与实践:以“开源AI智能名片2+1链动模式S2B2C商城小程序源码”为核心

摘要&#xff1a;在数字经济蓬勃发展的今天&#xff0c;中国作为全球数字经济的领航者&#xff0c;正以前所未有的速度推进“数字中国”建设。本文旨在探讨“开源AI智能名片21链动模式S2B2C商城小程序源码”在数字经济背景下的应用潜力与实践价值&#xff0c;从多个维度分析其对…...

【English-Book】Go in Action目录页翻译中文

第8页 内容 前言 xi 序言 xiii 致谢 xiv 关于本书 xvi 关于封面插图 xix 1 介绍 Go 1 1.1 用 Go 解决现代编程挑战 2 开发速度 3 • 并发 3 • Go 的类型系统 5 内存管理 7 1.2 你好&#xff0c;Go 7 介绍 Go 玩具 8 1.3 总结 8 2 Go 快速入门 9 2.1 程序架构 10 2.2 主包 …...

js: 区分后端返回数字是否为null、‘-’ 或正常number类型数字。

问&#xff1a; 这是我的代码<CountTo v-if!isNaN(Number(item.num))> <span v-else>{{item.num}}</span> 我希望不是null的时候走countTo&#xff0c;是null的时候直接<span>{{item.num}}</span>显示 回答&#xff1a; 最终结果&#xff1a; …...

网络变压器的分类

网络变压器是局域网(LAN)中各级网络设备中必备的元件。它们的主要功能是传输数据&#xff0c;增强信号&#xff0c;并提供电气隔离&#xff0c;以防雷保护和匹配阻抗。网络变压器也被称为数据泵或网络隔离变压器。它们广泛应用于网络交换机、路由器、网卡、集线器等设备中。 网…...

SUCTF-SU_BBRE-好久不见21

哈哈哈哈哈哈&#xff0c;&#xff0c;&#xff0c;&#xff0c;纯汇编有大佬用工具反编译成伪代码吗。。。 题解&#xff1a; 由function2处逻辑&#xff0c;解rc4得到第一段flag We1com3ToReWorld&#xff0c;正常输入下执行完function0&#xff0c;程序结束&#xff0c;cong…...

Python 实现 NLP 的完整流程

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…...

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>N 皇后

题目&#xff1a; 解析&#xff1a; 1.决策树&#xff1a; 代码设计&#xff1a; 根据决策树剪枝设计&#xff1a; 代码&#xff1a; class Solution {private List<List<String>> ret;private char[][] path;private boolean[] checkdig1,checkdig2,checkco…...

JEL分类号

JEL分类系统&#xff0c;是美国经济学会“经济文献杂志”(《经济文献杂志》)所创立的对经济学文献的主题分类系统&#xff0c;并被现代西方经济学界广泛采用。 该分类方法主要采用开头的一个英文字母与随后的两位阿拉伯数字一起对经济学各部类进行“辞书式”编码分类。 https:…...

设计和优化用于 AR、HUD 和高级显示系统的表面浮雕光栅

表面浮雕光栅是许多光学系统中的关键组件&#xff0c;在控制增强现实 &#xff08;AR&#xff09; 显示器、平视显示器 &#xff08;HUD&#xff09; 和其他先进光子器件中的光传播方面发挥着关键作用。作为在这个领域工作的工程师和设计师&#xff0c;您了解针对特定应用优化这…...

【今日分享】人工智能加速发现能源新材料的结构与性能

人工智能与材料国际学术会议(ICAIM)workshop9是由来自宁夏大学材料与新能源学院副院长王海龙教授及马薇副教授、杜鑫老师组成&#xff0c;他们将以“人工智能加速发现新能源新材料的结构与性能”为主题开展研讨工作&#xff0c;欢迎对该主题感兴趣的专家学者携稿加入。 loadin…...

Boost Asio TCP异步服务端和客户端

服务端 消息分两次发送&#xff0c;第一次发送head&#xff0c;第二次发送body。接收也是先接收head&#xff0c;然后通过head结构中的body长度字段再接收body。 TcpServer.h #pragma once #include <atomic> #include <vector> #include <unordered_set> #…...

1.7 ChatGPT:引领AI对话革命的致胜之道

ChatGPT:引领AI对话革命的致胜之道 随着人工智能(AI)技术的迅猛发展,特别是在自然语言处理(NLP)领域,OpenAI 的 ChatGPT 已经成为了举世瞩目的技术突破。从普通的自动化客服到深入的创作与协作,ChatGPT 通过其卓越的语言理解和生成能力,改变了人们与计算机交互的方式…...

销型网站建设必须的步骤包括/市场营销活动策划方案

ROC 曲线&#xff0c;作为评价机器学习模型敏感度的一条重要曲线&#xff0c;在分类任务评价机制中应用较多。但是很多朋友对于 ROC 曲线的理解还是有些模糊&#xff0c;心想着 x 轴是 FPR, y 轴是 TPR, 组条曲线有些神秘。今天&#xff0c;咱们用 4 个样本&#xff0c;使用逻辑…...

做推广网站的去哪能买到有效资料/深圳优化网站方法

RedisInsight 是一个直观高效的 Redis GUI 管理工具&#xff0c;它可以对 Redis 的内存、连接数、命中率以及正常运行时间进行监控&#xff0c;并且可以在界面上使用 CLI 和连接的 Redis 进行交互&#xff08;RedisInsight 内置对 Redis 模块支持&#xff09;&#xff1a; http…...

山东做网站公司哪家好/临沂google推广

文章目录10.1 Java与线程10.1.1 线程的实现1. 使用内核线程实现2. 使用用户线程实现3. 混合实现4.Java线程的实现10.1.2 Java线程调度10.1.3 状态转换10.1 Java与线程 10.1.1 线程的实现 线程的引入&#xff0c;可以把一个进程的资源分配和执行调度分开&#xff0c;各个线程既…...

梧州网站建设推广/怎么做网站优化

1.查看有无安装过mysqlrpm -qa|grep mysql2.查看有无安装包yum list mysql*3.虚拟机关掉eth0&#xff0c;联网后&#xff0c;安装mysql服务yum install mysql-serveryum install mysql-devel4.开启eth0&#xff0c;Xshell连接后&#xff0c;启动&&停止服务(1)在mysql配…...

怎样做可以连接服务器的网站/关键词营销优化

初始Linux操作系统&#xff08;手把手带你拿捏&#xff0c;一步一步进行操作讲解&#xff09; 备注&#xff1a; 楼主不才&#xff0c;不喜勿喷&#xff0c;若有错误或需要改进的地方&#xff0c;非常感谢你的指出&#xff0c;我会积极学习采纳。谢谢家人们一直以来的支持和鼓…...

网站权重不稳定/深圳疫情防控最新消息

传统的HTML5的下拉框select只能实现简单的文字下拉列表&#xff0c;而HT for Web通用组件中ComboBox不仅能够实现传统HTML5下拉框效果&#xff0c;而且可以在文本框和下拉列表中添加自定义的小图标&#xff0c;让整个组件看起来更直观&#xff0c;今天我就如何制定ComboBox自定…...