C# 实现消息去重

1 篇文章 0 订阅

之前在做微信服务号的时候会出现消息重复发送的情况,查阅了一些资料实现了消息去重的效果

具体流程是这样的

服务端接收消息,服务响应客户端(将此消息保存到消息队列中),服务端再次接收到消息,判断消息队列中是否存在此消息(通过时间来确定或者通过消息ID来确定,消息是否已经发送过),若存在则表示已经发送过该消息返回null值,若不存在则继续发送消息,对于延迟的消息也放入到队列中。

主要的中心点就是,你确保一个字段或者数据表明消息的唯一性,因为大部分服务对于消息延迟会有重复请求应答的情况,就会出现重发消息。

代码如下:

public string Executes(XmlElement rootElement)
        {
            if (_queue == null)
            {
                _queue = new List<BaseMsg>();
            }
            else if (_queue.Count >= 50)
            {
                _queue = _queue.Where(q => { return q.CreateTime.AddSeconds(20) > DateTime.Now; }).ToList();//保留20秒内未响应的消息
            }
            RequstXmlModel WxXmlModel = new RequstXmlModel();
            WxXmlModel.ToUserName = rootElement.SelectSingleNode("ToUserName").InnerText;
            WxXmlModel.FromUserName = rootElement.SelectSingleNode("FromUserName").InnerText;
            WxXmlModel.CreateTime = rootElement.SelectSingleNode("CreateTime").InnerText;
            WxXmlModel.MsgType = rootElement.SelectSingleNode("MsgType").InnerText;
            if (WxXmlModel.MsgType != "event")
            {
                if (_queue.FirstOrDefault(m => { return m.MsgFlag == WxXmlModel.MsgID; }) == null)
                {
                    _queue.Add(new BaseMsg
                    {
                        CreateTime = DateTime.Now,
                        FromUser = WxXmlModel.FromUserName,
                        MsgFlag = WxXmlModel.MsgID
                    });
                }
                else
                {
                    return null;
                }

            }
            else
            {
                if (_queue.FirstOrDefault(m => { return m.MsgFlag == WxXmlModel.CreateTime; }) == null)
                {
                    _queue.Add(new BaseMsg
                    {
                        CreateTime = DateTime.Now,
                        FromUser = WxXmlModel.FromUserName,
                        MsgFlag = WxXmlModel.CreateTime
                    });
                }
                else
                {
                    return null;
                }
            }
            switch (WxXmlModel.MsgType)
            {
                case "text"://文本
                    WxXmlModel.Content = rootElement.SelectSingleNode("Content").InnerText;
                    break;
                case "event"://事件
                    #region 事件处理
                    WxXmlModel.Event = rootElement.SelectSingleNode("Event").InnerText;
                    string Code = string.Empty;
                    switch (WxXmlModel.Event)
                    {
                        case "subscribe"://关注类型
                            LogTextHelper.WriteLog("关注扫码了");
                            WxXmlModel.EventKey = rootElement.SelectSingleNode("EventKey").InnerText;
                            Code = WxXmlModel.EventKey.Replace("qrscene_", "");
                            //LogTextHelper.WriteLog("参数加密:" + Code);
                            //LogTextHelper.WriteLog("参数解密:" + decHelper.Decrypto(Code));
                            //if (!IsExist(Code))//用来判断是否已经扫过二维码
                            //{
                            //EditWeChatMenu.DeleteMenu();//删除菜单
                            //EditWeChatMenu.CreateMenu();//创建自定义菜单
                            if (Code.Contains("加密了:"))
                            {
                                Code = Code.Replace("加密了:", "");
                                LogTextHelper.WriteLog("参数加密:" + Code);
                                LogTextHelper.WriteLog("参数解密:" + decHelper.Decrypto(Code));
                                LogTextHelper.WriteLog("序列化完的数据:" + JsonConvert.DeserializeObject(decHelper.Decrypto(Code)));
                                string getTemplateData = PutTemplateMsg(decHelper.Decrypto(Code));//WXHttpPost.HttpPostData("http://PharmaOne.winning.com.cn/api/Template/PutTemplateMsg", decHelper.Decrypto(Code));
                                dataObj data = JsonConvert.DeserializeObject<dataObj>(getTemplateData);
                                ResponeTemplate.SendTemplate(this.templatedSend, WxXmlModel, this.templateId, data.data, data.url);
                            }
                            else
                            {
                                ResponeTemplate.ResponeTemplateMsgTest(this.templateId, this.templatedSend, WxXmlModel, url2);
                            }
                            //}
                            //else
                            //{
                            //    WxXmlModel.Event = "二维码已失效";
                            //    WxXmlModel.Content = "二维码已失效";
                            //}
                            break;
                        case "SCAN"://扫码类型
                            LogTextHelper.WriteLog("扫码了");
                            WxXmlModel.EventKey = rootElement.SelectSingleNode("EventKey").InnerText;

                            //取到参数中的GUID判断是否已经被扫过【扫过的提示二维码已过期,没有扫过的就推送内容】
                            Code = WxXmlModel.EventKey;
                            if (Code.Contains("加密了:"))
                            {
                                Code = Code.Replace("加密了:", "");
                                LogTextHelper.WriteLog("参数加密:" + Code);
                                LogTextHelper.WriteLog("参数解密:" + decHelper.Decrypto(Code));
                                LogTextHelper.WriteLog("序列化完的数据:" + JsonConvert.DeserializeObject(decHelper.Decrypto(Code)));
                                //string getTemplateData = temPApi.PutTemplateMsg(decHelper.Decrypto(Code));
                                string getTemplateData = PutTemplateMsg(decHelper.Decrypto(Code));
                                dataObj data = JsonConvert.DeserializeObject<dataObj>(getTemplateData);
                                LogTextHelper.WriteLog("模板数据:" + getTemplateData);
                                if (data.status == "200")
                                {
                                    LogTextHelper.WriteLog("模板数据data:" + data.data);
                                    LogTextHelper.WriteLog("模板数据url:" + data.url);
                                    ResponeTemplate.SendTemplate(this.templatedSend, WxXmlModel, this.templateId, data.data, data.url);
                                }
                                else
                                {
                                    return "";
                                }
                            }
                            else
                            {
                                ResponeTemplate.ResponeTemplateMsgTest(this.templateId, this.templatedSend, WxXmlModel, url2);
                            }
                            //if (!IsExist(Code))
                            //{
                            //LogTextHelper.WriteLog("序列化完的数据:" + JsonConvert.DeserializeObject(decHelper.Decrypto(Code)));
                            //
                            //LogTextHelper.WriteLog("返回的模板数据的数据:" + getTemplateData);    
                            //}
                            //else
                            //{
                            //    WxXmlModel.Event = "二维码已失效";
                            //    WxXmlModel.Content = "二维码已失效";
                            //}
                            break;
                    #endregion
                        case "TEMPLATESENDJOBFINISH"://推送模板消息成功事件
                            WxXmlModel.MsgID = rootElement.SelectSingleNode("MsgID").InnerText;
                            WxXmlModel.Status = rootElement.SelectSingleNode("Status").InnerText;
                            break;
                        default:
                            break;
                    }
                    break;
            }
            return ResponseXML(WxXmlModel);//回复消息
        }

建个静态列表_queue,用来存储消息列表,列表的类型是List<BaseMsg>.
在处理微信消息体前,首先判断列表是否实例化,如果没有实例化则实例化,否则判断列表的长度是否大于或等于50(这个可以自定义,用处就是微信并发的消息量),如果大于或等于50,则保留20秒内未响应的消息(5秒重试一次,总共重试3次,就是15秒,保险起见这里写20秒)。
获取当前消息体的消息类型,并根据_queue判断当前消息是否已经请求了。如果是事件则保存FromUser和创建时间。如果是普通消息则保存MsgFlag。:
BaseMsg类型定义如下:

public class BaseMsg
    {
        /// <summary>
        /// 发送者标识
        /// </summary>
        public string FromUser { get; set; }
        /// <summary>
        /// 消息表示。普通消息时,为msgid,事件消息时,为事件的创建时间
        /// </summary>
        public string MsgFlag { get; set; }
        /// <summary>
        /// 添加到队列的时间
        /// </summary>
        public DateTime CreateTime { get; set; }
    }

RequstXmlModels:

public class RequstXmlModel
    {

        /// <summary>
        /// 消息接收方微信号
        /// </summary>
        public string ToUserName { get; set; }
        /// <summary>
        /// 消息发送方微信号
        /// </summary>
        public string FromUserName { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
        public string CreateTime { get; set; }
        /// <summary>
        /// 信息类型 地理位置:location,文本消息:text,消息类型:image
        /// </summary>
        public string MsgType { get; set; }
        /// <summary>
        /// 信息内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 地理位置纬度
        /// </summary>
        public string Location_X { get; set; }
        /// <summary>
        /// 地理位置经度
        /// </summary>
        public string Location_Y { get; set; }
        /// <summary>
        /// 地图缩放大小
        /// </summary>
        public string Scale { get; set; }
        /// <summary>
        /// 地理位置信息
        /// </summary>
        public string Label { get; set; }
        /// <summary>
        /// 图片链接,开发者可以用HTTP GET获取
        /// </summary>
        public string PicUrl { get; set; }
        /// <summary>
        /// 事件类型,subscribe(订阅/扫描带参数二维码订阅)、unsubscribe(取消订阅)、CLICK(自定义菜单点击事件) 、SCAN(已关注的状态下扫描带参数二维码)
        /// </summary>
        public string Event { get; set; }
        /// <summary>
        /// 事件KEY值
        /// </summary>
        public string EventKey { get; set; }
        /// <summary>
        /// 二维码的ticket,可以用来换取二维码
        /// </summary>
        public string Ticket { get; set; }
        /// <summary>
        /// 模板消息回复ID
        /// </summary>
        public string MsgID { get; set; }
        /// <summary>
        /// 回复状态
        /// </summary>
        public string Status { get; set; }
        /// <summary>
        /// 商品编码标准
        /// </summary>
        public string KeyStandard { get; set; }
        /// <summary>
        /// 商品编码内容
        /// </summary>
        public string KeyStr { get; set; }
        /// <summary>
        /// 获取商品二维码接口时传入的extinfo,为标识参数
        /// </summary>
        public string ExtInfo { get; set; }
        /// <summary>
        /// 图片点击的链接
        /// </summary>
        public string Url { get; set; }
        /// <summary>
        /// 图文信息的标题
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 图文信息的详情
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// 指菜单ID,如果是个性化菜单,则可以通过这个字段,知道是哪个规则的菜单被点击了。
        /// </summary>
        public string MenuID { get; set; }
        /// <summary>
        /// 扫描类型,一般是qrcode
        /// </summary>
        public string ScanType { get; set; }
        /// <summary>
        /// 扫描结果,即二维码对应的字符串信息
        /// </summary>
        public string ScanResult { get; set; }
    }

这样,我在每次请求微信服务的时候,在有时间延迟的情况下,服务端的应答就只能发送一条回复了,在做响应的时候就判断返回的对象是否为null,为null就不做respone操作,这就实现了消息去重。

如果有更好的建议,可以多交流。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新时代丘鸣山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值