实际项目中的消息中心

article/2025/9/1 12:49:45

前一篇文章讲到我们项目的工作流,这一篇我们扒一扒项目中的消息中心,msgcenter。消息可以分成很多种消息:留存可重复查看的DB消息,短暂保存在redis的comet消息,短信形式的msg消息,推送到手机的push消息等等。但是不管是哪种类型的消息,在我们的项目中都有统一的格式。(不管是db,msg或者是push都是一样的格式),这样就能保证存储和显示已经推送的一致性。

项目中的消息中心也是独立的模块,用maven依赖被各个网关层使用,当然简单方便,不过后来看了一篇文章,讲到微服务,我就在想是不是可以把消息模块独立出来做一个微服务,这样对消息中心做的优化就不用再修改别的网关应用了。不过这个是架构师哥哥处理的问题,我就是这么简单的想想。

废话不说,我们看msgcenter的层次

还是统一的层次,因为是模型,没有集成web层。dal是数据库持久层,domain是模型层(最接近模型的层),facade层是对外提供能力的层(外观模式,定义了对外提供能力的接口),biz是实现facade层的实例层,integration是集成其他模块的层(这一层很有意思,到时候我们单独写一篇文章介绍),test是测试层

那既然我们的消息中心是一个模型,供其他系统调用,那我们就看下facade对外提供的能力吧。

看facade层只有两个接口类(我们只关注MsgSenderFacade接口),

public interface MsgSenderFacade {
    /**
     * 发送消息
     *
     * @param list
     * @return
     */
    List<MsgBodyDTO> sendMsgs(List<MsgBodyDTO> list);

我们简单的看下发送消息的方法,系统中所有的消息调用的都是这个方法,是不是很屌,原因上面我们讲到过,不管什么类型的消息,他们都有统一的格式,那就是MsgBodyDTO,里面就是我们平常类的ID,消息模板ID,模板的替换数组,接收用户的ID,接收用户的电话号码,业务号,标题,真实内容,创建时间,更新时间,状态等等属性,我们看下实现方法

     fillIdAndStatus(allMsgList);//填充ID,创建时间,初始状态等等
        Map<MsgTypeEnum, List<MsgBodyDTO>> map = splitByMsgType(allMsgList);//根据消息队列的不同消息模板,分开(之后分别处理)
        try {
            for (Map.Entry<MsgTypeEnum, List<MsgBodyDTO>> entry : map
                    .entrySet()) {
                MsgTypeEnum msgType = entry.getKey();
                List<MsgBodyDTO> msgList = entry.getValue();
                switch (msgType) {
                case COMET:
                    LogUtil.info(logger,
                            "start send comet msgs[" + msgList.size() + "]");
                    if (logger.isDebugEnabled()) {
                        LogUtil.debug(logger, JSON.toJSONString(msgList));
                    }
                    cometMsgSenderProcessor.checkParams(msgList);
                    cometMsgSenderProcessor.doSend(msgList);//不同的消息类型我们用不同的消息处理器进行验证和分发。
                    break;
                case DB:
                    LogUtil.info(logger, "start send db msgs[" + msgList.size()
                            + "]");
                    dbMsgSenderProcessor.checkParams(msgList);
                    dbMsgSenderProcessor.doSend(msgList);
                    break;
                case MSG:
                    LogUtil.info(logger,
                            "start send mobile msgs[" + msgList.size() + "]");
                    mobileMsgSenderProcessor.checkParams(msgList);
                    mobileMsgSenderProcessor.doSend(msgList);
                    break;
                case PUSH:
                    LogUtil.info(logger,
                            "start send push msgs[" + msgList.size() + "]");
                    pushMsgSenderProcessor.checkParams(msgList);
                    pushMsgSenderProcessor.doSend(msgList);
                    break;
                default:
                    throw new BizException(
                            ErrorConst.ERROR_SUCH_MESSAGE_NOT_SUPPORT,
                            "不支持当前的消息类型[" + msgType.name() + "]");
                }
            }
            return allMsgList;

这四个处理器都是Spring注入的,我们看下配置,

<bean id="mc_cometMsgSenderProcessor" class="com.opengroup.hongshi.msgcenter.biz.msgSender.processor.comet.CometMsgSenderProcessorImpl" init-method="init"></bean>
    <bean id="mc_dbMsgSenderProcessor" class="com.opengroup.hongshi.msgcenter.biz.msgSender.processor.db.DBMsgSenderProcessorImpl"></bean>
    <bean id="mc_pushMsgSenderProcessor" class="com.opengroup.hongshi.msgcenter.biz.msgSender.processor.push.PushMsgSenderProcessorImpl"></bean>
    <bean id="mc_mobileMsgSenderProcessor" class="com.opengroup.hongshi.msgcenter.biz.msgSender.processor.msg.MobileMsgSenderProcessorImpl" init-method="init">
        <property name="channelList">
            <list>
                <ref bean="mc_channel_lanliang"/>
                <ref bean="mc_channel_qxt"/>
                <ref bean="mc_channel_shiyuan"/>
                <ref bean="mc_channel_yingxiao" />
            </list>
        </property>
    </bean>


我们接下来跟进代码查看具体实现方法 以mobileMsgSenderProcessor为例:


        //检查消息不能重复
        checkMsgsValid(domainMsgs);
        for (MsgBody msgBody : domainMsgs) {
            if (realSendMobileMsg) {
                MsgChannel channel = channelMap.get(msgBody.getChannelId());
                if (channel == null) {
                    StringBuilder buffer = new StringBuilder();
                    buffer.append("没有找到channelId=[").append(msgBody.getChannelId()).append("]的定义");
                    throw new CriticalSystemError(buffer.toString());
                } else {
                    String mobile = msgBody.getMobile();
                    try {
                        boolean success = channel.doSend(msgBody);
                        if (success) {
                            LogUtil.info(logger, "send mobile msg success[" + msgBody.getMobile()
                                                 + "][" + msgBody.getRealContent() + "]");
                            kvClient.save(
                                "msg_ok_" + mobile,
                                "["
                                        + DateUtil.format(new Date(),
                                            DateFormatterEnum.DATE_WITH_TIME.getCode()) + "]"
                                        + msgBody.getRealContent(), Integer.MAX_VALUE);
                        } else {
                            LogUtil.error(
                                Logs.ERROR_LOGGER,
                                "send mobile msg fail[" + msgBody.getMobile() + "]["
                                        + msgBody.getRealContent() + "]");
                        }
                    } catch (Exception e) {
                        LogUtil.error(
                            Logs.ERROR_LOGGER,
                            "send mobile msg fail[" + msgBody.getMobile() + "]["
                                    + msgBody.getRealContent() + "]", e);
                    } finally {
                        kvClient.save("msg_idx_" + mobile, msgBody.getChannelId(), 86400);
                    }
                }

标绿的channel就是代表我们当时配置的发送短信的通道,这个通道里面就是调用各大运营商的短信服务了。其实这个msgcenter比较简单,配置起来也不太麻烦。但是一开始调用的时候自己也摸不清头脑,不过一旦自己静下心来反而就看懂了,怕的就是自己浮躁。


http://chatgpt.dhexx.cn/article/CXcPQEpO.shtml

相关文章

消息中心

1 系统结构 消息中心体系结构如下图所示&#xff1a; 图中红色线表示移动消息的推送路径。 此结构适用于企业消息中心&#xff0c;也适用于平台&#xff0c;消息推送代理的消息推送服务接口&#xff08;Web Service&#xff09;可以作为开放服务。 本地服务器是消息源。…

消息中心(系统消息)实现

需求 用户能即时的收到来自系统或者其他用户发来的消息&#xff0c;在web界面右下角弹窗提醒&#xff0c;用户还能标记消息是否已阅状态。 即时通讯 概念&#xff1a;即时通讯&#xff08;实时通信&#xff0c;Instant Messaging&#xff0c;简称IM&#xff09;是一个实时通…

消息中心设计

1 参考文档 产品参考&#xff1a;消息通知系统设计 | 人人都是产品经理 (woshipm.com) 2 消息中心目标职责 消息中心仅作为消息发送使用&#xff0c;跟业务没有任何关系&#xff0c;涉及到业务部分有业务系统自行处理&#xff1b;消息中心的功能只有消息生产、消息展示、消息推…

如何设计一个消息中心

如今的内容型产品&#xff0c;不管提供的是什么类型的内容&#xff0c;在其主功能之外&#xff0c;不可避免的会有另一个十分重要的功能——消息中心。 而无论是信息流、论坛、信箱&#xff0c;还是私聊、群聊、通知&#xff0c;推拉模型是内容型&#xff08;包括&#xff1a;社…

聊聊消息中心的设计与实现逻辑

厌烦被消息打扰&#xff0c;又怕突然间的安静&#xff1b; 一、业务背景 微服务的架构体系中&#xff0c;会存在很多基础服务&#xff0c;提供一些大部分服务都可能需要的能力&#xff0c;比如文件管理、MQ队列、缓存机制、消息中心等等&#xff0c;这些服务需要提供各种可以复…

4. 消息中心的设计与实现

消息中心的设计与实现 一、引言 运用场景&#xff1a; 1、消息的主动提醒&#xff08;客户端被动接收&#xff09; 2、客户模块&#xff08;及时通讯&#xff09; 3、单一登录&#xff08;一个账号只能在一个设备登录&#xff09; 消息中心的实现方案&#xff1a; 1、客户端轮…

mysql格式化数字去掉千分位

前言 使用format格式化数字时&#xff0c;超过1000就会有千分位&#xff0c;但有时我们不希望有这个千分位&#xff0c;那怎么去掉呢&#xff1f; 1. select format(11111.123,2) 2. select convert(11111.123,decimal(12,2))

upper mysql_MySQL函数

MySQL函数 Lower 转换小写 upper 转换大写 substr 取子串(substr(被截取的字符串,起始下标,截取的长度)) length 取长度 trim 去空格 str_to_date 将字符串转换成日期 date_format 格式化日期 format 设置千分位 round 四舍五入 rand() 生成随机数 Ifnull 可以将null转换成一个…

MySql FORMAT 去掉千位分隔符,

加上墨西哥的地区参数即可 SELECT FORMAT(35555566.8, 2, es_MX) AS value; 结果

MySQL知识总结

目录 知识点条件查询排序常见单行处理函数&#xff08;可嵌套&#xff09;多行处理函数分组查询distinct去重 连接查询⭐⭐⭐⭐⭐内连接外连接多表连接&#xff08;两张表以上&#xff09; 子查询where子句中的子查询from 子句中的子查询select后面出现的子查询 union合并查询结…

MySQL知识点

总结汇总MySQL数据库面试题&#xff08;2020最新版&#xff09;_ThinkWon的博客-CSDN博客_mysql数据库面试题 1. 索引 &#xff08;1&#xff09;主键索引 唯一非空&#xff0c;属于聚簇索引 &#xff08;2&#xff09;唯一索引 unique 可为空(多个null也可) &#xff08;3&…

mysql 处理金额_MYSQL处理金额相关函数

1.FORMAT()数字千分位分割 FORMAT(X,D) 1.X需要格式化的数字 2.D保留小数位数 例:SELECT FORMAT(12334555.213,2) 2.ABS() 求绝对值 ABS(X) SELECT ABS(-23); SELECT ABS(21-23); SELECT ABS(23); 3.四舍五入保留小数 ROUND(X,D) 1.X需要格式化的数字 2.D保留小数位数(不写时默…

C语言字符数组的输入和输出

字符数组的输入输出有两种方法&#xff1a; &#xff08;1&#xff09;逐个字符输入输出。用格式符“%c”输入或输出一个字符。例如 int main() {char c[6]; //定义一个字符串for (int i 0; i < 5; i){scanf("%c", &c[i]); //输入字符串}for (int i 0; …

C语言 | 字符数组

C语言字符数组的定义 字符数组是用来存放字符数据的数组&#xff0c;字符数组中的一个元素存放一个字符&#xff0c;定义字符数组的方法和定义数值型数组的方法类似。 //例子&#xff1a;char character[10];C语言字符数组的初始化 C语言对字符数组初始化&#xff0c;最容易理解…

C语言,字符数组与字符串

文章目录 字符数组基本介绍字符串注意事项字符串的访问和遍历字符串的表示形式用字符数组存放一个字符串&#xff1a; 用字符数组存放一个字符串,用字符指针指向一个字符串使用字符指针变量和字符数组两种方法表示字符串的讨论 字符串相关函数常用字符串函数一览字符串函数应用…

浅谈字符数组

文章目录 一、什么是字符数组二、字符数组的定义和赋值1. 先定义一个数组&#xff0c;再为挨个元素进行赋值2. 在定义的同时给该字符数组进行初始化2.1 错误的初始化方式2.2 正确的初始化方式 3. 特别注意&#xff01;&#xff01;&#xff01;3.1 字符数组和整型数组一样不支持…

字符数组

字符数组 用来存放字符数据的数组是字符数组。字符数组中的每一个元素存放一个字符&#xff0c;其定义和使用方法与其他类型的数据基本相似。 1.1字符数组的定义和使用 字符数组的定义与其他类型的数组类似&#xff0c;标准形式如下&#xff1a; Char 数组名 【常量表达式】 …

Stata:嵌套Logit模型(NestedLogit)

原文链接&#xff1a;https://www.lianxh.cn/news/d5e00bfb17a7c.html 致谢&#xff1a; 这篇推文的核心内容主要来自陈强老师编著的《高级计量经济学及 Stata 应用》一书&#xff0c;特此致谢。 1. 简介 此前&#xff0c;连享会发布了一系列离散选择模型相关的推文&#xff0…

MNL——多项Logit模型学习笔记(二)

本节将会通过案例举例&#xff0c;介绍Logit模型的建模思路和过程 内容为摘抄他人学习资料的个人学习笔记&#xff0c;如有侵权则删 1.正确打开/解读Logit模型系数的方式 本节的具体内容在笔记里不详细表示了&#xff0c;大家在软件里拟合Logit模型时&#xff0c;对于其中的参…

logit回归模型的参数估计过程_【DCM07】Random Parameter(随机参数)Logit模型及其Nlogit实现...

本文是离散选择模型系列的第7篇原创文章,将详细介绍随机参数Logit模型,并利用Nlogit软件进行实操演示。【关注本公众号,可以获取数据和代码】本文公式较多,建议电脑端享用。 目录 1、随机参数Logit模型介绍 2、数据描述 3、随机参数Logit模型的参数估计 1 随机参数Logit模型…