【开源项目】消息推送平台austin介绍

news/2024/5/21 18:57:21/文章来源:https://blog.csdn.net/qq_42985872/article/details/129930921

项目介绍

核心功能:统一的接口发送各种类型消息,对消息生命周期全链路追踪。

意义:只要公司内部有发送消息的需求,都应该要有类似austin的项目。消息推送平台对各类消息进行统一发送处理,这有利于对功能的收拢,以及提高业务需求开发的效率。

项目地址https://github.com/ZhongFuCheng3y/austin

项目拆解

下发消息接口,分为群发和单发。接口参数主要有模板id(发送消息的内容模板),参数[用来替换模板参数,接收人],api可以存在多个,但是具体处理的方法只需要批量参数就可以。

单发的接口请求实体类

public class SendRequest {/*** 执行业务类型* send:发送消息* recall:撤回消息*/private String code;/*** 消息模板Id* 【必填】*/private Long messageTemplateId;/*** 消息相关的参数* 当业务类型为"send",必传*/private MessageParam messageParam;}

群发的接口实体类

public class BatchSendRequest {/*** 执行业务类型* 必传,参考 BusinessCode枚举*/private String code;/*** 消息模板Id* 必传*/private Long messageTemplateId;/*** 消息相关的参数* 必传*/private List<MessageParam> messageParamList;}

单个消息的实体

public class MessageParam {/*** @Description: 接收者* 多个用,逗号号分隔开* 【不能大于100个】* 必传*/private String receiver;/*** @Description: 消息内容中的可变部分(占位符替换)* 可选*/private Map<String, String> variables;/*** @Description: 扩展参数* 可选*/private Map<String, String> extra;
}

消息模板中定义了发送渠道,消息渠道决定消息的处理器。

AssembleAction转换实体类。核心逻辑有

  • 将模板中的可变参数转化成文本内容
  • 根据消息渠道获取消息实体类
  • 生成业务编码

AssembleAction#getContentModelValueContentHolderUtil.replacePlaceHolder(originValue, variables);用来处理可替换变量。

private static ContentModel getContentModelValue(MessageTemplate messageTemplate, MessageParam messageParam) {// 得到真正的ContentModel 类型Integer sendChannel = messageTemplate.getSendChannel();Class<? extends ContentModel> contentModelClass = ChannelType.getChanelModelClassByCode(sendChannel);// 得到模板的 msgContent 和 入参Map<String, String> variables = messageParam.getVariables();JSONObject jsonObject = JSON.parseObject(messageTemplate.getMsgContent());// 通过反射 组装出 contentModelField[] fields = ReflectUtil.getFields(contentModelClass);ContentModel contentModel = ReflectUtil.newInstance(contentModelClass);for (Field field : fields) {String originValue = jsonObject.getString(field.getName());if (StrUtil.isNotBlank(originValue)) {String resultValue = ContentHolderUtil.replacePlaceHolder(originValue, variables);Object resultObj = JSONUtil.isJsonObj(resultValue) ? JSONUtil.toBean(resultValue, field.getType()) : resultValue;ReflectUtil.setFieldValue(contentModel, field, resultObj);}}// 如果 url 字段存在,则在url拼接对应的埋点参数String url = (String) ReflectUtil.getFieldValue(contentModel, LINK_NAME);if (StrUtil.isNotBlank(url)) {String resultUrl = TaskInfoUtils.generateUrl(url, messageTemplate.getId(), messageTemplate.getTemplateType());ReflectUtil.setFieldValue(contentModel, LINK_NAME, resultUrl);}return contentModel;
}

SendMqAction负责发送消息,根据austin.mq.pipeline的值获取不同的消息发送者,有MQ发消息,有EventBus,有SpringEvent。

@Slf4j
@Service
public class SendMqAction implements BusinessProcess<SendTaskModel> {@Autowiredprivate SendMqService sendMqService;@Value("${austin.business.topic.name}")private String sendMessageTopic;@Value("${austin.business.recall.topic.name}")private String austinRecall;@Value("${austin.business.tagId.value}")private String tagId;@Value("${austin.mq.pipeline}")private String mqPipeline;@Overridepublic void process(ProcessContext<SendTaskModel> context) {SendTaskModel sendTaskModel = context.getProcessModel();try {if (BusinessCode.COMMON_SEND.getCode().equals(context.getCode())) {String message = JSON.toJSONString(sendTaskModel.getTaskInfo(), new SerializerFeature[]{SerializerFeature.WriteClassName});sendMqService.send(sendMessageTopic, message, tagId);} else if (BusinessCode.RECALL.getCode().equals(context.getCode())) {String message = JSON.toJSONString(sendTaskModel.getMessageTemplate(), new SerializerFeature[]{SerializerFeature.WriteClassName});sendMqService.send(austinRecall, message, tagId);}} catch (Exception e) {context.setNeedBreak(true).setResponse(BasicResultVO.fail(RespStatusEnum.SERVICE_ERROR));log.error("send {} fail! e:{},params:{}", mqPipeline, Throwables.getStackTraceAsString(e), JSON.toJSONString(CollUtil.getFirst(sendTaskModel.getTaskInfo().listIterator())));}}}

ConsumeServiceImpl#consume2Send,负责处理消息

    @Overridepublic void consume2Send(List<TaskInfo> taskInfoLists) {String topicGroupId = GroupIdMappingUtils.getGroupIdByTaskInfo(CollUtil.getFirst(taskInfoLists.iterator()));for (TaskInfo taskInfo : taskInfoLists) {logUtils.print(LogParam.builder().bizType(LOG_BIZ_TYPE).object(taskInfo).build(), AnchorInfo.builder().ids(taskInfo.getReceiver()).businessId(taskInfo.getBusinessId()).state(AnchorState.RECEIVE.getCode()).build());Task task = context.getBean(Task.class).setTaskInfo(taskInfo);taskPendingHolder.route(topicGroupId).execute(task);}}

Task#run,负责任务的具体处理

    @Overridepublic void run() {// 0. 丢弃消息if (discardMessageService.isDiscard(taskInfo)) {return;}// 1. 屏蔽消息shieldService.shield(taskInfo);// 2.平台通用去重if (CollUtil.isNotEmpty(taskInfo.getReceiver())) {deduplicationRuleService.duplication(taskInfo);}// 3. 真正发送消息if (CollUtil.isNotEmpty(taskInfo.getReceiver())) {handlerHolder.route(taskInfo.getSendChannel()).doHandler(taskInfo);}}

EmailHandler#handler,邮件处理器专门处理邮件的消息

    @Overridepublic boolean handler(TaskInfo taskInfo) {EmailContentModel emailContentModel = (EmailContentModel) taskInfo.getContentModel();MailAccount account = getAccountConfig(taskInfo.getSendAccount());try {File file = StrUtil.isNotBlank(emailContentModel.getUrl()) ? AustinFileUtils.getRemoteUrl2File(dataPath, emailContentModel.getUrl()) : null;String result = Objects.isNull(file) ? MailUtil.send(account, taskInfo.getReceiver(), emailContentModel.getTitle(), emailContentModel.getContent(), true) :MailUtil.send(account, taskInfo.getReceiver(), emailContentModel.getTitle(), emailContentModel.getContent(), true, file);} catch (Exception e) {log.error("EmailHandler#handler fail!{},params:{}", Throwables.getStackTraceAsString(e), taskInfo);return false;}return true;}

ShieldServiceImpl#shield,发送消息判断是否需要白天屏蔽。将消息存储到Redis中,开启xxl-job从redis中获取数据。

    @Overridepublic void shield(TaskInfo taskInfo) {if (ShieldType.NIGHT_NO_SHIELD.getCode().equals(taskInfo.getShieldType())) {return;}/*** example:当消息下发至austin平台时,已经是凌晨1点,业务希望此类消息在次日的早上9点推送* (配合 分布式任务定时任务框架搞掂)*/if (isNight()) {if (ShieldType.NIGHT_SHIELD.getCode().equals(taskInfo.getShieldType())) {logUtils.print(AnchorInfo.builder().state(AnchorState.NIGHT_SHIELD.getCode()).businessId(taskInfo.getBusinessId()).ids(taskInfo.getReceiver()).build());}if (ShieldType.NIGHT_SHIELD_BUT_NEXT_DAY_SEND.getCode().equals(taskInfo.getShieldType())) {redisUtils.lPush(NIGHT_SHIELD_BUT_NEXT_DAY_SEND_KEY, JSON.toJSONString(taskInfo,SerializerFeature.WriteClassName),(DateUtil.offsetDay(new Date(), 1).getTime() / 1000) - DateUtil.currentSeconds());logUtils.print(AnchorInfo.builder().state(AnchorState.NIGHT_SHIELD_NEXT_SEND.getCode()).businessId(taskInfo.getBusinessId()).ids(taskInfo.getReceiver()).build());}taskInfo.setReceiver(new HashSet<>());}}/*** 小时 < 8 默认就认为是凌晨(夜晚)** @return*/private boolean isNight() {return LocalDateTime.now().getHour() < 8;}

总结一下

  1. 下发消息接口,分为群发和单发。接口参数主要有模板id(发送消息的内容模板),模板中定义了消息渠道(消息类型决定消息的处理器),参数[用来替换模板参数,接收人],api可以存在多个,但是具体处理的方法只需要批量参数就可以。
  2. 根据模板组装数据,替换变量。
  3. 消息发送,使用监听器或者消息队列,进行异步解耦。消息发送的业务和消息接收的业务拆解开来。
  4. 根据发送渠道获取对应的消息处理器(用邮件还是短信),使用策略模式进行不同的消息渠道拆分,用具体的消息处理器进行处理消息。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_280009.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode每日一题:2367. 算术三元组的数目:暴力模拟+Hash表】

&#x1f34e;作者简介&#xff1a;硕风和炜&#xff0c;CSDN-Java领域新星创作者&#x1f3c6;&#xff0c;保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享&#x1f48e;&#x1f48e;&#x1f48e; &#x1f34e;座右…

windows下切换PHP-cli版本

概念&#xff1a; PHP-cli是PHP Command Line Interface的缩写&#xff0c;指的是PHP的命令行界面。它是PHP语言中的一种执行方式&#xff0c;可以在命令行下运行PHP程序&#xff0c;而不需要通过Web服务器或浏览器来执行。 PHP-cli可以用于很多场景&#xff0c;比如&#xf…

Spark—idea

ideal—Spark新建工程新建maven工程&#xff0c;添加scala添加依赖pom添加依赖并下载 spark-core重复步骤下载spark-sqlspark-hivespark-graphxmysql-connector-java安装完成新建SparkDemo的scala objectsc对象可能会出现报错&#xff0c;参考这位老哥的解决办法wordcount打架包…

快递的旅行日记 - 深度挖掘快递物流地图轨迹查询API 的使用场景

写在前面 全球化经济的不断发展使得快递业变得越来越重要&#xff0c;而快递物流地图轨迹查询 API 也因此应运而生。 该 API 可以帮助用户追踪物流信息&#xff0c;了解快递的运输状态&#xff0c;方便快递企业、个人用户以及电商平台等多方面的需求。 快递物流地图轨迹查询 …

Matlab进阶绘图第13期—带填充纹理的堆叠图

带填充纹理的堆叠图是通过在原始堆叠图的基础上添加不同的纹理得到的&#xff0c;可以很好地解决由于颜色区分不够而导致的对象识别困难问题。 由于Matlab中未收录提供填充纹理选项&#xff0c;因此需要大家自行设法解决。 本文使用hatchfill2工具&#xff08;Kesh Ikuma. Ma…

JVM 字符串常量池(StringTable)

String的基本特性 String&#xff1a;字符串&#xff0c;使用一对 “” 引起来表示String s1 "hello" ; // 字面量的定义方式String s2 new String("hello"); // new 对象的方式 String被声明为final的&#xff0c;不可被继承 String实现了…

【新股打新】北森控股:发行比例只有区区1%,这是要作妖的前奏?

一、公司简介 北森控股&#xff0c;成立于2002年&#xff0c;是国内领先的人力资源管理的高科技公司&#xff0c;旗下的一体化HR SaaS及人才管理平台 —— iTalentX&#xff0c;覆盖了企业招聘、入职、管理到离职的全生命周期的数字化管理&#xff0c;帮助企业快速提升人力资源…

玄子Share-BCSP助学手册之数据库开发(已优化)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NIr0tYNc-1680505151717)(./assets/XuanZiShare_QQ_3336392096.jpg)] 玄子Share-BCSP助学手册之数据库开发 前言&#xff1a; 此文为玄子&#xff0c;学习 BCSP 一二期后整理的文章&#xff0c;文中对…

Vivado中如何修改IP源文件

前一篇文章是通过改变JESD204B IP的设置&#xff0c;在Shared Logic里勾选in example design&#xff0c;来避免共用输入时钟的问题。那么还有没有别的办法呢&#xff1f;有没有更直接点的实现方式呢&#xff1f; 答案是肯定的&#xff1a;可以直接修改IP&#xff0c;将IBUFDS…

开心档之开发入门网-C++ 变量类型

C 变量类型 目录 C 变量类型 C 中的变量定义 C 中的变量声明 实例 实例 C 中的左值&#xff08;Lvalues&#xff09;和右值&#xff08;Rvalues&#xff09; 变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有指定的类型&#xff0c;类型决定了变量存储的大小…

合宙Air780E|ScreenStream|图传|LuatOS-SOC接口|学习(23):4G远程遥控小车(2)-图传设置及解析

目录 基础资料 原项目地址 实现功能&#xff1a; 前文&#xff1a; 图传设置及解析 概述 提示 软件亮点 操作步骤 4G小车控制前端相关代码 图传显示函数&#xff1a; 按钮及显示框 待解决问题&#xff1a;小车图传前端不能正常显示 基础资料 ​ 基于Air780E开发板&…

「Python 机器学习」Matplotlib 数据探索

Matplotlib 是一个 Python 的数据可视化库&#xff0c;它能够轻松创建各种类型的图表和图形&#xff1b;Matplotlib 可以在 Jupyter Notebooks、交互式应用程序和脚本中使用&#xff0c;并支持多种绘图样式和格式&#xff1b; Matplotlib 最初是为科学计算而设计的&#xff0c…

【观察】诺基亚贝尔品牌焕新传递新价值,以无线专网加速中国数字化进程

今年2月底&#xff0c;在西班牙巴塞罗那举行的2023年世界移动通信大会上&#xff0c;诺基亚宣布重塑企业战略和技术战略&#xff0c;同时推出全新企业品牌形象&#xff0c;这标志着诺基亚在长期战略转型之路上迈出了坚实的一步。而作为诺基亚在华独家运营实体&#xff0c;诺基亚…

pdf格式可以编辑吗?提供几个思路

PDF格式文件常常用于共享文档和保护文档内容&#xff0c;但是很多人都会遇到一个问题&#xff1a;PDF格式文件是否可编辑&#xff1f; 答案是肯定的&#xff0c;PDF格式文件是可以编辑的。虽然PDF格式文件的初衷是为了保护文档内容&#xff0c;但是现在很多软件已经支持PDF格式…

【OJ比赛日历】快周末了,不来一场比赛吗? #04.01-04.07 #14场

CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号同时会推送最新的比赛消息&#xff0c;欢迎关注&#xff01;更多比赛信息见 CompHub主页 或 点击文末阅读原文以下信息仅供参考&#xff0c;以比赛官网为准目录2023-04-01&…

Android OKHttp源码解析

Https是Http协议加上下一层的SSL/TSL协议组成的&#xff0c;TSL是SSL的后继版本&#xff0c;差别很小&#xff0c;可以理解为一个东西。进行Https连接时&#xff0c;会先进行TSL的握手&#xff0c;完成证书认证操作&#xff0c;产生对称加密的公钥、加密套件等参数。之后就可以…

【人工智能】—局部搜索算法、爬山法、模拟退火、局部剪枝、遗传算法

Local search algorithms &#xff08;局部搜索算法&#xff09;局部搜索算法内存限制局部搜索算法示例&#xff1a;n-皇后爬山算法随机重启爬山模拟退火算法局部剪枝搜索遗传算法小结局部搜索算法 在某些规模太大的问题状态空间内&#xff0c;A*往往不够用 问题空间太大了无法…

InVEST模型

详情点击链接&#xff1a;invest模型 生态系统服务理论联系实践案例InVEST模型的开发历程、不同版本的差异及对数据需求InVEST所需数据的要求&#xff08;分辨率、格式、投影系统等&#xff09;、获取及标准化预处理InVEST运行常见问题及处理解决方法ArcGIS工具支撑InVEST模型…

PropertySourceLocator(SpringCloud中的配置操作)

又是美好的一天呀~ 个人博客地址&#xff1a; huanghong.top 往下看看~内容简介源码分析prepareContextapplyInitializersPropertySourceBootstrapConfiguration#initializelocateCollectionNacos示例insertPropertySourcesConfigurationPropertiespostProcessBeforeInitializa…

[python]浅谈Flask的SSTI漏洞

目录 基础知识 python类方法 内建函数 获取基类的几种方法 利用思路 概念简介 服务器端模板注入&#xff08;Server-Side Template Injection&#xff09; 类型判断 简单探测 实战练习 reference 基础知识 python类方法 __class__用来查看变量所属的类&#xff0c…