基于模板方法模式-消息队列发送
基于模板方法模式-消息队列发送
消息队列广泛应用于现代分布式系统中,作为解耦、异步处理和流量控制的重要工具。在消息队列的使用中,发送消息是常见的操作。不同的消息队列可能有不同的实现方式,例如,RabbitMQ、Kafka、RocketMQ、Redis等。为了统一操作流程和复用代码,可以使用设计模式来简化开发工作。
模板方法模式(Template Method Pattern)是一种行为型设计模式,旨在通过定义一个操作的骨架,而将一些步骤延迟到子类中实现。在消息队列发送场景中,模板方法模式可以帮助我们定义消息发送的基本流程,同时将具体的发送操作抽象为子类来实现,从而避免重复的代码。
1. 使用 RocketMQ 发送普通消息
public class YourServiceCode {@Autowiredprivate RocketMQTemplate rocketMQTemplate;@Autowiredprivate ConfigurableEnvironment configurableEnvironment;@Transactional(rollbackFor = Exception.class)public void yourMethod(YourRequestDTO requestParam) {// 业务逻辑:// ...... 省略业务处理部分// 如果是立即发送任务,直接调用消息队列进行发送if (Objects.equals(requestParam.getSendType(), YourSendTypeEnum.IMMEDIATE.getType())) {// 定义消息队列 Topic(根据需要调整实际的 Topic)String topic = "your-topic-name-${unique-name}";// 通过 Spring 上下文解析占位符,把模板中的 unique-name 替换为实际值topic = configurableEnvironment.resolvePlaceholders(topic);// 构建消息体String messageKeys = UUID.randomUUID().toString();Message<Long> message = MessageBuilder.withPayload(yourDO.getId()) // 使用具体的 DO 对象.setHeader(MessageConst.PROPERTY_KEYS, messageKeys) // 设置消息的 Key.build();// 执行消息队列发送及异常处理逻辑SendResult sendResult;try {sendResult = rocketMQTemplate.syncSend(topic, message, 2000L); // 发送消息,并设置超时时间log.info("[生产者] 执行任务 - 发送结果:{},消息ID:{},消息Keys:{}", sendResult.getSendStatus(), sendResult.getMsgId(), messageKeys);} catch (Exception ex) {log.error("[生产者] 执行任务 - 消息发送失败,消息体:{}", yourDO.getId(), ex); // 异常处理}}}
}
每次发送消息时,总是充斥着大量相同的冗余代码,这些逻辑散落在业务代码中,不利于对核心业务的理解和维护。
2.什么是模板方法模式
模板方法模式(Template Method Pattern)是一种行为型设计模式,旨在通过在父类中定义一个算法的框架(即模板方法),而将某些步骤延迟到子类中实现。模板方法模式让子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。
在模板方法模式中:
- 父类提供了一个骨架方法(即模板方法),并规定了算法的步骤。
- 子类负责实现某些具体的步骤,这些步骤通常是一些可变的行为,父类并不关心。
- 父类的模板方法调用这些可变的步骤,完成一个完整的算法流程。
通过这种方式,可以确保算法的结构一致,但又允许各个子类根据需要修改特定的步骤。
3. 使用模板方法模式的好处
使用模板方法模式后,我们可以将发送消息的流程固定,并把具体的步骤通过子类来实现。这样带来了以下好处:
- 代码复用:通过模板方法模式,可以将发送消息的共同逻辑提取到父类中,避免了重复代码。例如,连接消息队列和错误处理等通用逻辑只需在父类中实现。
- 清晰的流程控制:模板方法模式能够清晰地定义消息发送的流程,所有子类遵循同一流程,便于理解和调试。
- 易于扩展:如果需要支持不同的消息队列,只需要创建不同的子类实现具体的发送逻辑,父类的通用流程不变,符合开闭原则,便于扩展。
- 高内聚低耦合:每个消息队列的具体实现仅依赖于模板方法模式中的抽象方法,具有很高的内聚性,同时也保持了与其他模块的低耦合。
4.模板方法设计模式重构消息发送
4.1 定义消息发送事件基础实体
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public final class BaseSendExtendDTO {/*** 事件名称*/private String eventName;/*** 主题*/private String topic;/*** 标签*/private String tag;/*** 业务标识*/private String keys;/*** 发送消息超时时间*/private Long sentTimeout;/*** 具体延迟时间*/private Long delayTime;
}
另外,有些和业务无关的属性,我们再抽象一层 Wrapper 类,用于定义消息发送基础内容。
@Data
@Builder
@NoArgsConstructor(force = true)
@AllArgsConstructor
@RequiredArgsConstructor
public final class MessageWrapper<T> implements Serializable {private static final long serialVersionUID = 1L;/*** 消息发送 Keys*/@NonNullprivate String keys;/*** 消息体*/@NonNullprivate T message;/*** 消息发送时间*/private Long timestamp = System.currentTimeMillis();
}
4.2 定义抽象消息发送类
将消息发送的逻辑和结果日志的打印进行了抽象,也就是抽象方法模式中的复用性。并且,将消息发送事件的基本参数(如 Topic、Tag、是否延迟消息等)以及 Keys 的个性化属性独立为两个抽象方法。
@RequiredArgsConstructor
@Slf4j(topic = "CommonSendProduceTemplate")
public abstract class AbstractCommonSendProduceTemplate<T> {private final RocketMQTemplate rocketMQTemplate;/*** 构建消息发送事件基础扩充属性实体** @param messageSendEvent 消息发送事件* @return 扩充属性实体*/protected abstract BaseSendExtendDTO buildBaseSendExtendParam(T messageSendEvent);/*** 构建消息基本参数,请求头、Keys...** @param messageSendEvent 消息发送事件* @param requestParam 扩充属性实体* @return 消息基本参数*/protected abstract Message<?> buildMessage(T messageSendEvent, BaseSendExtendDTO requestParam);/*** 消息事件通用发送** @param messageSendEvent 消息发送事件* @return 消息发送返回结果*/public SendResult sendMessage(T messageSendEvent) {BaseSendExtendDTO baseSendExtendDTO = buildBaseSendExtendParam(messageSendEvent);SendResult sendResult;try {// 构建 Topic 目标落点 formats: `topicName:tags`StringBuilder destinationBuilder = StrUtil.builder().append(baseSendExtendDTO.getTopic());if (StrUtil.isNotBlank(baseSendExtendDTO.getTag())) {destinationBuilder.append(":").append(baseSendExtendDTO.getTag());}// 延迟时间不为空,发送任意延迟消息,否则发送普通消息if (baseSendExtendDTO.getDelayTime() != null) {sendResult = rocketMQTemplate.syncSendDeliverTimeMills(destinationBuilder.toString(),buildMessage(messageSendEvent, baseSendExtendDTO),baseSendExtendDTO.getDelayTime());} else {sendResult = rocketMQTemplate.syncSend(destinationBuilder.toString(),buildMessage(messageSendEvent, baseSendExtendDTO),baseSendExtendDTO.getSentTimeout());}log.info("[生产者] {} - 发送结果:{},消息ID:{},消息Keys:{}", baseSendExtendDTO.getEventName(), sendResult.getSendStatus(), sendResult.getMsgId(), baseSendExtendDTO.getKeys());} catch (Throwable ex) {log.error("[生产者] {} - 消息发送失败,消息体:{}", baseSendExtendDTO.getEventName(), JSON.toJSONString(messageSendEvent), ex);throw ex;}return sendResult;}
}
4.3. 定义消息发送事件
将消息队列中的数据定义为事件
4.4定义消息队列生产者
消息队列生产者继承了我们的消息发送抽象类,并实现了两个抽象方法,从而体现了模板方法设计模式的扩展性。在业务代码中,只需引入消息发送生产者,即可通过简洁的一行代码完成消息发送流程。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CouponTaskExecuteEvent {/*** 推送任务id*/private Long couponTaskId;
}@Slf4j
@Component
public class CouponTaskActualExecuteProducer extends AbstractCommonSendProduceTemplate<CouponTaskExecuteEvent> {private final ConfigurableEnvironment environment;public CouponTaskActualExecuteProducer(@Autowired RocketMQTemplate rocketMQTemplate, @Autowired ConfigurableEnvironment environment) {super(rocketMQTemplate);this.environment = environment;}@Overrideprotected BaseSendExtendDTO buildBaseSendExtendParam(CouponTaskExecuteEvent messageSendEvent) {return BaseSendExtendDTO.builder().eventName("优惠券推送执行").keys(String.valueOf(messageSendEvent.getCouponTaskId())).topic(environment.resolvePlaceholders("你的主题")).sentTimeout(2000L).build();}@Overrideprotected Message<?> buildMessage(CouponTaskExecuteEvent couponTaskExecuteEvent, BaseSendExtendDTO requestParam) {String keys = StrUtil.isEmpty(requestParam.getKeys()) ? UUID.randomUUID().toString() : requestParam.getKeys();return MessageBuilder.withPayload(new MessageWrapper(keys, couponTaskExecuteEvent)).setHeader(MessageConst.PROPERTY_KEYS, keys).setHeader(MessageConst.PROPERTY_TAGS, requestParam.getTag()).build();}
}
相关文章:
基于模板方法模式-消息队列发送
基于模板方法模式-消息队列发送 消息队列广泛应用于现代分布式系统中,作为解耦、异步处理和流量控制的重要工具。在消息队列的使用中,发送消息是常见的操作。不同的消息队列可能有不同的实现方式,例如,RabbitMQ、Kafka、RocketMQ…...
俄语画外音的特点
随着全球媒体消费的增加,语音服务呈指数级增长。作为视听翻译和本地化的一个关键方面,画外音在确保来自不同语言和文化背景的观众能够以一种真实和可访问的方式参与内容方面发挥着重要作用。说到俄语,画外音有其独特的特点、挑战和复杂性&…...
PyTorch使用教程(10)-torchinfo.summary网络结构可视化详细说明
1、基本介绍 torchinfo是一个为PyTorch用户量身定做的开源工具,其核心功能之一是summary函数。这个函数旨在简化模型的开发与调试流程,让模型架构一目了然。通过torchinfo的summary函数,用户可以快速获取模型的详细结构和统计信息࿰…...
亚博microros小车-原生ubuntu支持系列:5-姿态检测
MediaPipe 介绍参见:亚博microros小车-原生ubuntu支持系列:4-手部检测-CSDN博客 本篇继续迁移姿态检测。 一 背景知识 以下来自亚博官网 MediaPipe Pose是⼀个⽤于⾼保真⾝体姿势跟踪的ML解决⽅案,利⽤BlazePose研究,从RGB视频…...
C语言之高校学生信息快速查询系统的实现
🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 C语言之高校学生信息快速查询系统的实现 目录 任务陈述与分析 问题陈述问题分析 数据结构设…...
WPF基础 | WPF 基础概念全解析:布局、控件与事件
WPF基础 | WPF 基础概念全解析:布局、控件与事件 一、前言二、WPF 布局系统2.1 布局的重要性与基本原理2.2 常见布局面板2.3 布局的测量与排列过程 三、WPF 控件3.1 控件概述与分类3.2 常见控件的属性、方法与事件3.3 自定义控件 四、WPF 事件4.1 路由事件概述4.2 事…...
迷宫1.2
先发一下上次的代码 #include<bits/stdc.h> #include<windows.h> #include <conio.h> using namespace std; char a[1005][1005]{ " ", "################", "# # *#", "# # # #&qu…...
RabbitMQ---应用问题
(一)幂等性介绍 幂等性是本身是数学中的运算性质,他们可以被多次应用,但是不会改变初始应用的结果 1.应用程序的幂等性介绍 包括很多,有数据库幂等性,接口幂等性以及网络通信幂等性等 就比如数据库的sel…...
Unity自学之旅03
Unity自学之旅03 Unity自学之旅03📝 碰撞体 Collider 基础定义与作用常见类型OnCollisionEnter 事件碰撞触发器 🤗 总结归纳 Unity自学之旅03 📝 碰撞体 Collider 基础 定义与作用 定义:碰撞体是游戏中用于检测物体之间碰撞的组…...
pip 相关
一劳永逸法(pip怎么样都用不了也更新不了): 重下python(卸载旧版本):请输入访问密码 密码:7598 各版本python都有,下3.10.10 python路径建立,pip无法访问方式: 访问pip要…...
vue request 发送formdata
在Vue中,你可以使用axios库来发送包含FormData的请求。以下是一个简单的例子: 首先,确保你已经安装了axios: npm install axios然后,你可以使用axios发送FormData,例如: import axios from a…...
Android RTMP直播练习实践
前言:本文只是练习,本文只是练习,本文只是练习! 直播的核心就是推流和拉流,我们就以RTMP的协议来实现下推流和拉流,其他的协议等我学习后再来补充 1.推流 1.1搭建流媒体服务器,具体搭建方法请参…...
ITIL认证工具商-ManageEngine Servicedesk Plus
ServiceDesk Plus是Zoho Corporation旗下企业IT管理部门ManageEngine提供的统一服务管理解决方案。凭借其无限的可扩展性、情境化的IT和业务集成以及一键式工作流程自动化功能,IT领导者可以使用ServiceDesk Plus有效执行和控制跨不同业务部门和IT功能的复杂工作流程…...
https 的 CA证书和电子签名
https 的攻击者可能使用伪造的一对公私钥与客户端交互, 那么如何确保确实是该服务器的公钥呢? 这就诞生了CA颁发机构 CA颁发机构 服务器和客户端都信任指定的CA颁发机构 服务器上传服务器公钥, CA颁发机构做了什么 服务器公钥哈希, 记为 Hash使用 CA 私钥为 Hash 进行 CA 签…...
频繁刷新网页会对服务器造成哪些影响?
当用户在进行浏览网页的过程中频繁刷新页面时,浏览器会向服务器发送请求,服务器会对该请求进行处理并返回到相应的页面内容中,所以频繁刷新网页会对服务器造成影响,有可能会出现以下问题: 用户每次刷新网页都会向服务器…...
贪心算法(题1)区间选点
输出 2 #include <iostream> #include<algorithm>using namespace std;const int N 100010 ;int n; struct Range {int l,r;bool operator <(const Range &W)const{return r<W.r;} }range[N];int main() {scanf("%d",&n);for(int i0;i&l…...
JavaWeb开发学习笔记--MySQL
MySQL-DQL 基本语法: select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段列表 limit 分页参数 基本查询 关键字:SELECT 查询多个字段:select 字…...
抖音小程序一键获取手机号
前端代码组件 <button v-if"!isFromOrderList"class"get-phone-btn" open-type"getPhoneNumber"getphonenumber"onGetPhoneNumber">一键获取</button>// 获取手机号回调onGetPhoneNumber(e) {var that this tt.login({f…...
iconfont等图标托管网站上传svg显示未轮廓化解决办法
打开即时设计 即时设计 - 可实时协作的专业 UI 设计工具 导入图标后拖入画板里面,右键选择轮廓化 将图标导出...
2008-2020年各省城镇登记失业率数据
2008-2020年各省城镇登记失业率数据 1、时间:2008-2020年 2、来源:国家统计局、统计年鉴 3、指标:行政区划代码、地区名称、年份、城镇登记失业率 4、范围:31省 5、指标说明:城镇登记失业率是指在一定时期内&…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...
