快递企业如何完成运单订阅消息的推送

原文:快递企业如何完成运单订阅消息的推送

  经常网购的朋友,会实时收到运单状态的提醒信息,这些提醒信息包括微信推送,短信推送,邮件推送,支付宝生活窗推送,QQ推送等,信息内容主要包括快件到哪里,签收等信息的提醒,这些友好的提醒信息会极大的增强购物者的体验。

  笔者目前正在一家快递企业做这类消费消息的推送功能开发(大部分快递企业都有实现在客户寄完快件后可以主动接收到快递企业的运单状态推送信息),对这部分有一些体会,现分享给大家(大部分功能可能只能通过代码才方便体现出来)。

订阅和推送的流程图

一、订阅功能:提供微信、短信、邮件等的订阅服务,用户在下完单以后系统自动完成运单状态的订阅功能

 订阅功能调试

  订阅实现的原理:实现订阅主要是把当前的单号和订阅者的关联信息存储到Redis缓存中(之所以要存在Redis中,大家应该都好理解,主要是数据量大的时候,处理速度块)

    下面提供微信订阅的主要功能代码,通过代码及注释信息可以看到订阅服务的原理。

namespace DotNet.Subscriber
{
    using Utilities;
    using Business;

    /// <summary>
    /// Subscriber
    /// 基于订单的消息订阅
    ///
    /// 修改纪录
    ///
    /// 2016-12-16 版本:1.0 SongBiao 创建文件。
    ///
    /// <author>
    ///     <name>SongBiao</name>
    ///     <date>2016-12-16</date>
    /// </author>
    /// </summary>
    public partial class Subscriber
    {
        /// <summary>
        /// 微信单号订阅(根据微信用户OpenId)
        /// </summary>
        /// <param name="userInfo">用户信息,或接口用户</param>
        /// <param name="systemCode">系统编号</param>
        /// <param name="billCode">运单号码</param>
        /// <param name="weChatOpenId">微信OpenId</param>
        /// <param name="subscriberType">订阅者类型 0:普通关注者;1:寄件人;2:收件人</param>
        /// <param name="checkBillCode">检查单号,true 检查,false 不检查,某些情况下不用检查,加快处理</param>
        /// <param name="subCompanyId">订阅者所在的公司主键,短信扣费要使用</param>
        /// <param name="subUserId">订阅者的用户主键,短信扣费要使用</param>
        /// <param name="scanType">扫描类型</param>
        /// <returns></returns>
        public static BaseResult SubWeChat(BaseUserInfo userInfo, string systemCode, string billCode, string weChatOpenId, SubscriberType subscriberType, bool checkBillCode = true, string subCompanyId = null, string subUserId = null, params int[] scanType)
        {
            var result = Subscribe(userInfo, systemCode, billCode, weChatOpenId, PushType.WeChat, subscriberType, "0", checkBillCode, subCompanyId, subUserId, scanType);
            return result;
        }

        /// <summary>
        /// 微信单号订阅(根据手机号)
        /// </summary>
        /// <param name="userInfo">用户信息,或接口用户</param>
        /// <param name="systemCode">系统编号</param>
        /// <param name="billCode">运单号码</param>
        /// <param name="mobile">手机号码</param>
        /// <param name="subscriberType">接收者类型 0:普通关注者;1:寄件人;2:收件人</param>
        /// <param name="checkBillCode">检查单号,true 检查,false 不检查,某些情况下不用检查,加快处理</param>
        /// <param name="subCompanyId">订阅者所在的公司主键,扣费要使用</param>
        /// <param name="subUserId">订阅者的用户主键,扣费要使用</param>
        /// <param name="scanType">扫描类型</param>
        /// <returns></returns>
        public static BaseResult SubWeChatByMobile(BaseUserInfo userInfo, string systemCode, string billCode, string mobile, SubscriberType subscriberType, bool checkBillCode = true, string subCompanyId = null, string subUserId = null, params int[] scanType)
        {
            var openId = WechatPublicBindManager.GetOpenIdByMobileByCache(mobile);
            if (string.IsNullOrWhiteSpace(openId))
            {
                return BaseResult.Error("该手机号码没有关注公司微信公众号。");
            }
            else
            {
                var result = SubWeChat(userInfo, systemCode, billCode, openId, subscriberType, checkBillCode, subCompanyId, subUserId, scanType);
                return result;
            }
        }

        /// <summary>
        /// 微信单号订阅(根据员工编号)
        /// </summary>
        /// <param name="userInfo">用户信息,或接口用户</param>
        /// <param name="systemCode">系统编号</param>
        /// <param name="billCode">运单号码</param>
        /// <param name="userCode">员工编号</param>
        /// <param name="subscriberType">接受者类型</param>
        /// <param name="checkBillCode">检查单号,true 检查,false 不检查,某些情况下不用检查,加快处理</param>
        /// <param name="subCompanyId">订阅者所在的公司主键,短信扣费要使用</param>
        /// <param name="subUserId">订阅者的用户主键,短信扣费要使用</param>
        /// <param name="scanType">扫描类型</param>
        /// <returns></returns>
        public static BaseResult SubWeChatByUserCode(BaseUserInfo userInfo, string systemCode, string billCode, string userCode, SubscriberType subscriberType, bool checkBillCode = true, string subCompanyId = null, string subUserId = null, params int[] scanType)
        {
            BaseResult result = BaseResult.Fail();
            if (result.Status)
            {
                var userEntity = BaseUserManager.GetObjectByCodeByCache(userCode);
                if (userEntity != null)
                {
                    var mobile = BaseUserContactManager.GetMobileByCache(userEntity.Id);
                    if (!string.IsNullOrWhiteSpace(mobile))
                    {
                        result = SubWeChatByMobile(userInfo, systemCode, billCode, mobile, subscriberType, checkBillCode, subCompanyId, subUserId, scanType);
                    }
                }
            }
            return result;
        }
    }
}

  部分重要的枚举类 :消息接收者(订阅者)类型,推送类型,扫描类型

    /// <summary>
    /// ReceiveType
    /// 消息接收者(订阅者)类型枚举
    /// 寄件人还是收件人:0:普通关注者;1:寄件人;2:收件人
    ///
    /// 修改记录
    ///
    ///        2016-12-17 版本:1.0 SongBiao 创建文件。
    ///
    /// <author>
    ///        <name>SongBiao</name>
    ///        <date>2016-12-17</date>
    /// </author>
    /// </summary>
    public enum SubscriberType
    {
        /// <summary>
        /// 普通关注者
        /// </summary>
        [EnumDescription("普通关注者")]
        Common = 0,
        /// <summary>
        /// 寄件人
        /// </summary>
        [EnumDescription("寄件人")]
        Sender = 1,
        /// <summary>
        /// 收件人
        /// </summary>
        [EnumDescription("收件人")]
        Receiver = 2
    }

    /// <summary>
    /// PushType
    /// 推送类型枚举
    /// 0:手机短信;1:微信;2:邮箱;3:QQ;4:支付宝 等
    ///
    /// 修改记录
    ///
    ///        2016-12-17 版本:1.0 SongBiao 创建文件。
    ///
    /// <author>
    ///        <name>SongBiao</name>
    ///        <date>2016-12-17</date>
    /// </author>
    /// </summary>
    public enum PushType
    {
        /// <summary>
        /// 手机短信
        /// </summary>
        [EnumDescription("手机短信")]
        Sms = 0,
        /// <summary>
        /// 微信
        /// </summary>
        [EnumDescription("微信")]
        WeChat = 1,
        /// <summary>
        /// 邮箱
        /// </summary>
        [EnumDescription("邮箱")]
        Email = 2,
        /// <summary>
        /// QQ
        /// </summary>
        [EnumDescription("QQ")]
        QQ = 3,
        /// <summary>
        /// 支付宝
        /// </summary>
        [EnumDescription("支付宝")]
        AliPay = 4,
        /// <summary>
        /// 菜鸟物流云短信
        /// </summary>
        [EnumDescription("菜鸟物流云短信")]
        CNWLYSms = 5
    }

    /// <summary>
    /// ScanType
    /// 扫描类型枚举
    ///
    /// 修改记录
    ///
    ///        2016-12-17 版本:1.0 SongBiao 创建文件。
    ///
    /// <author>
    ///        <name>SongBiao</name>
    ///        <date>2016-12-17</date>
    /// </author>
    /// </summary>
    public enum ScanType
    {
        /// <summary>
        /// 收件
        /// </summary>
        [EnumDescription("收件")]
        Receive = 0,
        /// <summary>
        /// 发件
        /// </summary>
        [EnumDescription("发件")]
        Send = 1,
        /// <summary>
        /// 到件
        /// </summary>
        [EnumDescription("到件")]
        Come = 2,
        /// <summary>
        /// 派件
        /// </summary>
        [EnumDescription("派件")]
        Disp = 3,
        /// <summary>
        /// 签收
        /// </summary>
        [EnumDescription("签收")]
        Sign = 4,
        /// <summary>
        /// 第三方派件
        /// </summary>
        [EnumDescription("第三方派件")]
        Otherps = 5,
    }

  订阅功能的核心代码,微信,短信,邮件等的订阅最终调用的方法

        /// <summary>
        /// 单号订阅
        /// 此方法是最终的调用方法,不允许外部直接调用
        /// </summary>
        /// <param name="userInfo">用户信息,或接口用户</param>
        /// <param name="systemCode">系统编号</param>
        /// <param name="billCode">运单号码</param>
        /// <param name="objectId">订阅的接收对象:手机号码,微信OpenId,邮箱,支付宝等唯一标示</param>
        /// <param name="pushType">推送方式,0:手机;1:微信;2:邮箱;3:QQ;4:支付宝 等</param>
        /// <param name="subscriberType">接收者类型,0:普通关注者;1:寄件人;2:收件人</param>
        /// <param name="pushTempleteId">推送模板主键,默认发送系统模板,梧桐客户端有维护功能,如某些特殊客户(小红书)发送自己的模板消息</param>
        /// <param name="checkBillCode">检查单号,true 检查,false 不检查,某些情况下不用检查,加快处理</param>
        /// <param name="subCompanyId">订阅者所在的公司主键,短信扣费要使用</param>
        /// <param name="subUserId">订阅者的用户主键,短信扣费要使用</param>
        /// <param name="scanType">订阅扫描类型,0:收;1:发;2:到;3:派;4:签</param>
        /// <returns></returns>
        private static BaseResult Subscribe(BaseUserInfo userInfo, string systemCode, string billCode, string objectId, PushType pushType, SubscriberType subscriberType = SubscriberType.Common, string pushTempleteId = "0", bool checkBillCode = true, string subCompanyId = null, string subUserId = null, params int[] scanType)
        {
            BaseResult baseResult = new BaseResult();
            baseResult.Status = false;
            baseResult.StatusMessage = "订阅失败";
// 检查单号的签收等业务信息
            if (checkBillCode)
            {
                baseResult = CheckBill(billCode);
                if (!baseResult.Status)
                {
                   return baseResult;
                }
            }

            if (string.IsNullOrWhiteSpace(billCode) || string.IsNullOrWhiteSpace(objectId))
            {
                baseResult.StatusMessage = "运单号或订阅的接收对象不可为空";
            }
            else if (scanType == null || scanType.Length == 0)
            {
                baseResult.StatusMessage = "扫描类型不可外空。";
            }
            else if (billCode.Contains(KeyNameSplit) || objectId.Contains(KeyNameSplit) || pushTempleteId.Contains(KeyNameSplit))
            {
                baseResult.StatusMessage = "运单编号、接收对象、模板中不可包含‘" + KeyNameSplit + "‘符号。";
            }
            else if (billCode.Contains(ValueSplit) || objectId.Contains(ValueSplit) || pushTempleteId.Contains(ValueSplit))
            {
                baseResult.StatusMessage = "运单编号、接收对象、模板中不可包含‘" + ValueSplit + "‘符号。";
            }
            else if (billCode.Contains(ValuesSeparator) || objectId.Contains(ValuesSeparator) || pushTempleteId.Contains(ValuesSeparator))
            {
                baseResult.StatusMessage = "运单编号、接收对象、模板中不可包含‘" + ValuesSeparator + "‘符号。";
            }
            else
            {
                try
                {
                    billCode = billCode.Trim();
                    objectId = objectId.Trim();
                    pushTempleteId = string.IsNullOrWhiteSpace(pushTempleteId) ? "0" : pushTempleteId.Trim();
                    // 订阅的公司主键 发送时涉及扣费
                    subCompanyId = string.IsNullOrWhiteSpace(subCompanyId) ? "0" : subCompanyId.Trim();
                    // 订阅的用户主键 发送时涉及扣费
                    subUserId = string.IsNullOrWhiteSpace(subUserId) ? "0" : subUserId.Trim();

                    // 使用下面方式可以自动实现过期,Hash不方便处理,14天自动从缓存移除
                    var redisClient = PooledRedisHelper.GetSubscriberClient();
                    {
                        // 缓存各种类型的扫描
                        foreach (int scan in scanType)
                        {
                            // 需要判断scan是否属于枚举类型数据, 不是的就不用处理
                            if (Enum.IsDefined(typeof(ScanType), scan))
                            {
                                // 扫描类型,订阅者(接受者)类型,单号做主键 key  如 BILLSUB:3:400028189727
                                string mKey = SetRedisKey((ScanType)scan, billCode);
                                string mValues = redisClient.GetValue(mKey);
                                // 推送类型,关注对象,模板主键作为值
                                // string mValue = string.Format("{0}" + objectId + "{0}" + pushTempleteId, ValueSplit);
                                string mValue = SetRedisValue((SubscriberType)subscriberType, (PushType)pushType, objectId, pushTempleteId, subCompanyId, subUserId);
                                if (string.IsNullOrWhiteSpace(mValues))
                                {
                                    mValues = mValue;
                                }
                                else
                                {
                                    if (!mValues.Contains(mValue))
                                    {
                                        mValues = mValues + ValuesSeparator + mValue;
                                    }
                                }
                                redisClient.SetEntry(mKey, mValues, new TimeSpan(ExpireiInDays, 0, 0, 0));
                            }
                        }

                        // 订阅接收对象和单号的关系存储
                        string objKey = GetSubscriberKey(objectId);
                        // 微信可能存储多个关注的单号
                        string revLists = redisClient.GetValue(objKey);
                        if (string.IsNullOrEmpty(revLists))
                        {
                            revLists = billCode;
                        }
                        else
                        {
                            if (!revLists.Contains(billCode))
                            {
                                revLists = revLists + ValuesSeparator + billCode;
                            }
                        }
                        // BILLSUB:18516093434  = > 400028189727||400028189728 如 表示某个手机号码,微信等订阅了哪些单号

                        redisClient.SetEntry(objKey, revLists, new TimeSpan(ExpireiInDays, 0, 0, 0));
                    }

                    baseResult.Status = true;
                    baseResult.StatusMessage = "订阅成功";
                    // 统计订阅信息
                    SubStatistics(systemCode, true);

                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "单号订阅出现异常");
                    // 统计订阅信息
                    SubStatistics(systemCode, false);

                    baseResult.StatusMessage = "异常:" + ex.Message;
                }
            }

            return baseResult;
        }

 注意:目前我们的客户在微信公众号上下单时,会将用户的手机号码和微信OpenId关联存储起来,如果用户需要邮件或手机短信推送,在下单时只要完善对应信息即可。

二、消息队列分发:也就是订单状态发生变化后,将当前最新的状态向订阅者的微信、手机或邮件等的推送,目前我使用消息队列来进行运单状态的消费推送服务,根据运单的不同状态,消息队列分为收件、发件、派件、到件、签收队列。

  由于推送过程(发生微信消息、短息消息、邮件消息)都是网络耗时的一些操作,为了保证消息队列服务的稳定,首先将待发送的信息按照订阅方式存储到Redis队列中(微信、短信、邮件等),然后由程序不断的消费这些Redis队列,这样可以保证消息队列不至于由于推送的异常造成堆积,实现原理代码:

  核心的任务调度

        #region 核心的任务调度方法
        /// <summary>执行工作</summary>
        /// <param name="index">线程序号</param>
        /// <returns></returns>
        public override bool Work(int index)
        {
            #region 业务数据的处理线程调度
            try
            {
                if (index == 0)
                {
                    // 主线程开启
                    CommonUtils.MainWork();
                }
                else if (index == 1)
                {
                    // 向监控系统报到 5分钟一次
                    CommonUtils.MonitorSign();
                }
                else if (index == 2)
                {
                    // 短信信推送
                    SMSMeSessageProcess.Send();
                }
                else if (index == 3)
                {
                    // 微信推送
                    WeChatMessageProcess.Send();
                }
                else if (index == 4)
                {
                    // 支付宝推送
                    AliPayMessageProcess.Send();
                }
                else if (index == 5)
                {
                    // 邮件推送
                    EmailMessageProcess.Send();
                }
                else if (index == 6)
                {
                    // 异常队列中数据的恢复
                    SMSMeSessageProcess.SetItemFromPop();
                    WeChatMessageProcess.SetItemFromPop();
                    EmailMessageProcess.SetItemFromPop();
                    AliPayMessageProcess.SetItemFromPop();
                }
                else
                {
                    if (!isPause)
                    {
                        //其它线程,负责处理业务数据
                    }
                    else
                    {
                        // 有可能会造成系统卡死 如果设置的线程数不准
                        //   Thread.Sleep(1000);
                    }
                }
            }
            catch (Exception ex)
            {
                // 判断如果是网络问题 进行重试
                //if (string.Equals("None of the specified endpoints were reachable", ex.Message, StringComparison.OrdinalIgnoreCase))
                string message = "运单消息推送服务线程调度异常,需要立即检查" + ex.Message;
                if (ex.Message.IndexOf("reachable", StringComparison.Ordinal) > -1)
                {
                    // 设置为未消费状态,自动会重新执行 因网络造成的异常,可以让服务重新启动
                    isConsumering = false;
                    message += ",网络异常,isConsumering=" + isConsumering;
                }

                WechatPublicBindManager.SendSystemException("o2DTtjk6ys-EKEnN6qbSC_TbteEw", "运单消息推送异常", "运单消息推送程序", message, ConfigurationHelper.AppSettings("ServerIp", false));
                XTrace.WriteException(ex);
                NLogHelper.InfoMail(ex, message);
                // 报告异常
                CommonUtils.MonitorSign(1, message);
            }
            #endregion

            return base.Work(index);
        }
        #endregion

主线程处理工作

        /// <summary>
        /// 主线程处理工作
        /// 主要是把消息队列的内容根据订阅分发存储到Redis队列中
        /// </summary>
        public static void MainWork()
        {
            Console.WriteLine(DateTime.Now + ",主消费线程开始启动。。。");

            string serviceName = GetDataServiceName();
            if (!string.IsNullOrEmpty(MaxThreadsSetting))
            {
                int.TryParse(MaxThreadsSetting, out maxThreads);
            }
            // 按照配置项设置的开启服务
            if (string.IsNullOrWhiteSpace(ServiceType))
            {
                // otherps 第三方派送
                ServiceType = "rec,come,disp,sign,otherps";
            }
            string[] serviceTypeArray = ServiceType.Split(‘,‘);

            // 收件消费启动
            if (serviceTypeArray.Contains("rec"))
            {
                if (RecConsumerClient == null)
                {
                    RecConsumerClient = MQFactory.GetConsumerClient(mqHost, mqPort, "scan.rec", mqKey, mqSercet, maxThreads);
                }
                if (RecConsumerClient == null)
                {
                    NLogHelper.Debug(serviceName + ":RecConsumerClient 返回为空");
                }
                else
                {
                    if (!RecConsumerClient.IsConnected() || !isRecConsumerClient)
                    {
                        RecConsumerClient.StartConsumer("que_push_scan.rec", new MessageProcessCallback(MessageProcess.RecMessageProcess));
                        isRecConsumerClient = true;
                        NLogHelper.Debug(serviceName + ":收件消息推送已启动");
                        WechatPublicBindManager.SendSystemException("o2DTtjk6ys-EKEnN6qbSC_TbteEw", "运单消息推送启动报告", "运单消息推送程序", serviceName + ":收件消息推送已启动", GetInternalIP());
                    }
                }
            }

            // 到件消费启动
            if (serviceTypeArray.Contains("come"))
            {
                if (ComeConsumerClient == null)
                {
                    ComeConsumerClient = MQFactory.GetConsumerClient(mqHost, mqPort, "scan.come", mqKey, mqSercet, maxThreads);
                }
                if (ComeConsumerClient == null)
                {
                    NLogHelper.Debug(serviceName + ":ComeConsumerClient 返回为空");
                }
                else
                {
                    if (!ComeConsumerClient.IsConnected() || !isComeConsumerClient)
                    {
                        ComeConsumerClient.StartConsumer("que_push_scan.come", new MessageProcessCallback(MessageProcess.ComeMessageProcess));
                        isComeConsumerClient = true;
                        NLogHelper.Debug(serviceName + ":到件消息推送已启动");
                        WechatPublicBindManager.SendSystemException("o2DTtjk6ys-EKEnN6qbSC_TbteEw", "运单消息推送启动报告", "运单消息推送程序", serviceName + ":到件消息推送已启动", GetInternalIP());
                    }
                }
            }

            // 派件消费启动
            if (serviceTypeArray.Contains("disp"))
            {
                if (DispConsumerClient == null)
                {
                    DispConsumerClient = MQFactory.GetConsumerClient(mqHost, mqPort, "scan.disp", mqKey, mqSercet, maxThreads);
                }
                if (DispConsumerClient == null)
                {
                    NLogHelper.Debug(serviceName + ":DispConsumerClient 返回为空");
                }
                else
                {
                    if (!DispConsumerClient.IsConnected() || !isDispConsumering)
                    {
                        DispConsumerClient.StartConsumer("que_push_scan.disp", new MessageProcessCallback(MessageProcess.DispMessageProcess));
                        isDispConsumering = true;
                        NLogHelper.Debug(serviceName + ":派送消息推送已启动");
                        WechatPublicBindManager.SendSystemException("o2DTtjk6ys-EKEnN6qbSC_TbteEw", "运单消息推送启动报告", "运单消息推送程序", serviceName + ":派送消息推送已启动", GetInternalIP());
                    }
                }
            }

            // 签收消费启动
            if (serviceTypeArray.Contains("sign"))
            {
                if (SignConsumerClient == null)
                {
                    SignConsumerClient = MQFactory.GetConsumerClient(mqHost, mqPort, "scan.sign", mqKey, mqSercet, maxThreads);
                }
                if (SignConsumerClient == null)
                {
                    NLogHelper.Debug(serviceName + ":SignConsumerClient 返回为空");
                }
                else
                {
                    if (!SignConsumerClient.IsConnected() || !isSignConsumering)
                    {
                        SignConsumerClient.StartConsumer("que_push_scan.sign", new MessageProcessCallback(MessageProcess.SignMessageProcess));
                        isSignConsumering = true;
                        NLogHelper.Debug(serviceName + ":签收消息推送已启动");
                        WechatPublicBindManager.SendSystemException("o2DTtjk6ys-EKEnN6qbSC_TbteEw", "运单消息推送启动报告", "运单消息推送程序", serviceName + ":签收消息推送已启动", GetInternalIP());
                    }
                }
            }

            // 第三方派送消费启动
            if (serviceTypeArray.Contains("otherps"))
            {
                if (OtherDispConsumerClient == null)
                {
                    OtherDispConsumerClient = MQFactory.GetConsumerClient(mqHost, mqPort, "scan.otherdisp", mqKey, mqSercet, maxThreads);
                }
                if (OtherDispConsumerClient == null)
                {
                    NLogHelper.Debug(serviceName + ":OtherDispConsumerClient 返回为空");
                }
                else
                {
                    if (!OtherDispConsumerClient.IsConnected() || !isOtherDispConsumering)
                    {
                        OtherDispConsumerClient.StartConsumer("queue_push_otherdisp", new MessageProcessCallback(MessageProcess.OtherDispMessageProcess));
                        isOtherDispConsumering = true;
                        NLogHelper.Debug(serviceName + ":第三方门店派送消息推送已启动");
                        WechatPublicBindManager.SendSystemException("o2DTtjk6ys-EKEnN6qbSC_TbteEw", "运单消息推送启动报告", "运单消息推送程序", serviceName + ":第三方门店派送消息推送已启动", GetInternalIP());
                    }
                }
            }
        }

 以上主要是实现消息队列的分发,将要发送的微信、短信、邮件等消息分别存储到不同的Redis队列中

三、消息推送:消费Redis队列中的微信、短信、邮件等待推送的信息,主要看下微信推送的功能代码,将微信队列中的消息收、发、到、派、签的状态依次完成发送

实时推送状态监控

推送实时统计图

  这一部分不是很复杂,主要注意消费出现异常时的处理。

  /// <summary>
    /// MessageProcess.SendWeChat
    /// 消息处理 派件消息处理
    ///
    /// 修改记录
    ///
    ///        2018.01.01 版本:1.0 SongBiao 创建。
    ///
    /// <author>
    ///        <name>SongBiao</name>
    ///        <date>2018.01.01</date>
    /// </author>
    /// </summary>

    public class WeChatMessageProcess
    {
        /// <summary>
        /// 缓存客户端
        /// </summary>
        private static BussinessCacheClient sbcc = null;

        public static bool IsRunning = true;
        public static string SetId = CommonUtils.MainRedisSetId + ":" + PushType.WeChat;
        public static readonly string PopTranHashId = "PTH" + ":" + CommonUtils.MainRedisSetId + ":" + PushType.WeChat;

        /// <summary>
        /// 将异常数据转移到正常队列中
        /// </summary>
        public static void SetItemFromPop()
        {
            while (true)
            {
                try
                {
                    var redisClient = PooledRedisPush.GetClient();
                    long count = redisClient.GetSortedSetCount(PopTranHashId);
                    if (count == 0)
                    {
                        break;
                    }
                    else
                    {
                        string item = redisClient.PopItemWithLowestScoreFromSortedSet(PopTranHashId);
                        if (!string.IsNullOrEmpty(item))
                        {
                            var model = item.FromRedisJson<MessageRedisEntity>();
                            redisClient.AddItemToSortedSet(SetId, model.ToRedisJson<MessageRedisEntity>(), model.CreateOn.Ticks);
                        }
                    }
                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "将异常数据转移到队列中异常 SetWechatItemFromPop");
                    break;
                }
            }
        }

        /// <summary>
        /// 发送微信提醒的消息
        /// </summary>
        public static void Send()
        {
            MessageRedisEntity messageRedis = null;
            string strMessageRedis = string.Empty;
            while (true)
            {
                if (!IsRunning)
                {
                    NLogHelper.Debug("SendWeChat() 停止运行:" + DateTime.Now);
                    break;
                }
                try
                {
                    // 使用手机短信发送的 收发到派签  消息推送
                    var redisClient = PooledRedisPush.GetClient();
                    long count = redisClient.GetSortedSetCount(SetId);
                    if (count == 0)
                    {
                        NLogHelper.Debug("SendWeChat() 没有待发送的消息," + DateTime.Now);
                        //System.Threading.Thread.Sleep(10000);
                        break;
                    }
                    else
                    {
                        using (RedisPopTransactionLockObject ptlo = redisClient.PopItemWithLowestScoreFromSortedSet(SetId, PopTranHashId, out strMessageRedis))
                        {
                            try
                            {
                                if (!string.IsNullOrEmpty(strMessageRedis))
                                {
                                    messageRedis = strMessageRedis.FromRedisJson<MessageRedisEntity>();
                                    if (messageRedis != null)
                                    {
                                        ScanType scanType = messageRedis.Scan;
                                        // 按扫描类型发送不同的消息
                                        switch (scanType)
                                        {
                                            case ScanType.Receive:
                                                ReceiveWeChat(messageRedis);
                                                break;

                                            case ScanType.Send:
                                                // 暂无订阅处理
                                                break;

                                            case ScanType.Come:
                                                ComeWeChat(messageRedis);
                                                break;

                                            case ScanType.Disp:
                                                DispWeChat(messageRedis);
                                                break;

                                            case ScanType.Sign:
                                                SignWeChat(messageRedis);
                                                break;

                                            case ScanType.Otherps:
                                                OtherDispWeChat(messageRedis);
                                                break;
                                            default:
                                                break;
                                        }
                                    }
                                }
                                ptlo.Commit();
                            }
                            catch (Exception ep)
                            {
                                ptlo.Rollback();
                                NLogHelper.Warn(ep, "SendWeChat()异常了,PopItemWithLowestScoreFromSortedSet");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "SendWeChat()异常了:" + strMessageRedis);
                    System.Threading.Thread.Sleep(10000);
                    break;
                }
            }
        }

        /// <summary>
        /// 发送收件微信提醒
        /// </summary>
        /// <param name="messageRedis"></param>
        public static void ReceiveWeChat(MessageRedisEntity messageRedis)
        {
            List<string> listWechat = new List<string>();
            string recSiteName = messageRedis.ScanSite;
            string recSiteId = string.Empty;
            // 收件推送这一部分取了订单的信息 根据订单信息里的手机号码判断是否由微信绑定
            if (sbcc == null)
            {
                sbcc = BussinessCacheHelper.GetBussinessCacheClient<BussinessCacheClient>("USC", "User", "Pass");
            }
            if (sbcc == null)
            {
                NLogHelper.Trace("ReceiveWeChat BussinessCacheHelper.GetBussinessCacheClient 返回NULL");
            }
            else
            {
                // 从订单获取的一部分 这一部分全部用微信推送,因此只要取得微信
                try
                {
                    List<OrderMainEntity> list = sbcc.GetBillOrderDatas(messageRedis.BillCode);
                    if (list != null && list.Any())
                    {
                        string wechatOpenId = string.Empty;
                        foreach (var model in list)
                        {
                            // 根据电话是否是手机,获取微信openid
                            if (!string.IsNullOrWhiteSpace(model.RecPhone) && ValidateUtilities.IsMobile(model.RecPhone))
                            {
                                // 根据手机号码获取微信openId
                                wechatOpenId = WechatPublicBindManager.GetOpenIdByMobileByCache(model.RecPhone);
                                if (!string.IsNullOrWhiteSpace(wechatOpenId))
                                {
                                    listWechat.Add(wechatOpenId);
                                }
                            }
                            // 根据手机获取微信openid
                            if (!string.IsNullOrWhiteSpace(model.RecMobile) && ValidateUtilities.IsMobile(model.RecMobile))
                            {
                                // 根据手机号码获取微信openId
                                wechatOpenId = WechatPublicBindManager.GetOpenIdByMobileByCache(model.RecMobile);
                                if (!string.IsNullOrWhiteSpace(wechatOpenId))
                                {
                                    listWechat.Add(wechatOpenId);
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "从订单获取数据,实现订阅的信息发送出现异常 ReceiveWeChat");
                }
            }

            try
            {
                List<string> subDataList = messageRedis.SubData;
                // strArray 是类似数组 2,0,openid,0,公司id,用户id  2,0,openid,0,公司id,用户id
                string[] array;
                foreach (var strArray in subDataList)
                {
                    array = strArray.Split(Subscriber.ValueSplit);
                    listWechat.Add(array[2]);
                }
                string message = string.Empty;
                if (!string.IsNullOrWhiteSpace(recSiteName))
                {
                    message = "已由网点" + recSiteName + "揽件发出";
                }
                else
                {
                    message = "已发出";
                }
                if (listWechat.Any())
                {
                    listWechat = listWechat.Distinct().ToList();
                    foreach (var item in listWechat)
                    {
                        SendBillStatusChanged(item, messageRedis.BillCode, messageRedis.Scan, message, string.Format("http://tuisong.kuaidi.cn/Track/Result?openId={0}&billCode={1}", item, messageRedis.BillCode));
                    }
                }
            }
            catch (Exception ex)
            {
                NLogHelper.Warn(ex, "发送收件微信提醒 ReceiveWeChat ,messageRedis=" + messageRedis.FastToJson());
                throw ex;
            }
        }

        /// <summary>
        /// 发送到件微信提醒
        /// </summary>
        /// <param name="messageRedis"></param>
        public static void ComeWeChat(MessageRedisEntity messageRedis)
        {
            List<string> listWechat = new List<string>();
            // 到件推送这一部分取了订单的信息 根据订单信息里的手机号码判断是否由微信绑定
            string comeSiteName = messageRedis.ScanSite;
            if (sbcc == null)
            {
                sbcc = BussinessCacheHelper.GetBussinessCacheClient<BussinessCacheClient>("USC", "User", "Pass");
            }
            if (sbcc == null)
            {
                NLogHelper.Trace("BussinessCacheHelper.GetBussinessCacheClient 返回NULL");
            }
            else
            {
                List<OrderMainEntity> list = sbcc.GetBillOrderDatas(messageRedis.BillCode);
                // 从订单获取的一部分 这一部分全部用微信推送,因此只要取得微信
                if (list != null && list.Any())
                {
                    string wechatOpenId = string.Empty;
                    foreach (var model in list)
                    {
                        // 根据电话是否是手机,获取微信openid
                        if (!string.IsNullOrWhiteSpace(model.RecPhone) && ValidateUtilities.IsMobile(model.RecPhone))
                        {
                            // 根据手机号码获取微信openId
                            wechatOpenId = WechatPublicBindManager.GetOpenIdByMobileByCache(model.RecPhone);
                            if (string.IsNullOrWhiteSpace(wechatOpenId))
                            {
                                listWechat.Add(wechatOpenId);
                            }
                        }
                        // 根据手机获取微信openid
                        if (!string.IsNullOrWhiteSpace(model.RecMobile) && ValidateUtilities.IsMobile(model.RecMobile))
                        {
                            // 根据手机号码获取微信openId
                            wechatOpenId = WechatPublicBindManager.GetOpenIdByMobileByCache(model.RecMobile);
                            if (string.IsNullOrWhiteSpace(wechatOpenId))
                            {
                                listWechat.Add(wechatOpenId);
                            }
                        }
                    }
                }
            }
            try
            {
                List<string> subDataList = messageRedis.SubData;
                string[] array;
                // strArray 是类似数组 2,0,openid,0,公司id,用户id  2,0,openid,0,公司id,用户id
                foreach (var strArray in subDataList)
                {
                    array = strArray.Split(Subscriber.ValueSplit);
                    listWechat.Add(array[2]);
                }
                string message = string.Empty;
                message = "已到达" + messageRedis.ScanSite;
                if (listWechat.Any())
                {
                    listWechat = listWechat.Distinct().ToList();
                    foreach (var item in listWechat)
                    {
                        SendBillStatusChanged(item, messageRedis.BillCode, messageRedis.Scan, message, string.Format("http://tuisong.kuaidi.cn/Track/Result?openId={0}&billCode={1}", item, messageRedis.BillCode));
                    }
                }
            }
            catch (Exception ex)
            {
                NLogHelper.Warn(ex, "发送到件微信提醒 ReceiveWeChat ,messageRedis=" + messageRedis.FastToJson());
                throw ex;
            }
        }

        /// <summary>
        /// 发送派件微信提醒
        /// </summary>
        /// <param name="messageRedis"></param>
        public static void DispWeChat(MessageRedisEntity messageRedis)
        {
            try
            {
                string message = string.Format("您的快件{0}由【{1}派件员{2},{3}】派送,请注意查收。", messageRedis.BillCode, messageRedis.DispatchSite, messageRedis.DispMan, messageRedis.DispManPhone);
                List<string> subDataList = messageRedis.SubData;
                List<string> listWechat = new List<string>();
                // strArray 是类似数组 2,0,openid,0,公司id,用户id  2,0,openid,0,公司id,用户id
                string[] array;
                foreach (var strArray in subDataList)
                {
                    array = strArray.Split(Subscriber.ValueSplit);
                    listWechat.Add(array[2]);
                }
                if (listWechat.Any())
                {
                    listWechat = listWechat.Distinct().ToList();
                    foreach (var item in listWechat)
                    {
                        SendDispWeChat(item, message, messageRedis.BillCode, messageRedis.Scan, messageRedis.DispManPhone);
                    }
                }
            }
            catch (Exception ex)
            {
                NLogHelper.Warn(ex, "发送派件微信提醒 DispWeChat ,messageRedis=" + messageRedis.FastToJson());
                throw ex;
            }
        }

        /// <summary>
        /// 发送签收微信提醒
        /// </summary>
        /// <param name="messageRedis"></param>
        public static void SignWeChat(MessageRedisEntity messageRedis)
        {
            string message = string.Format("您好,您的快件已签收,签收人是{0}", messageRedis.SignMan); ;
            List<string> subDataList = messageRedis.SubData;
            List<string> listWechat = new List<string>();
            // strArray 是类似数组 2,0,openid,0,公司id,用户id  2,0,openid,0,公司id,用户id
            string[] array;
            foreach (var strArray in subDataList)
            {
                array = strArray.Split(Subscriber.ValueSplit);
                listWechat.Add(array[2]);
            }
            if (listWechat.Any())
            {
                listWechat = listWechat.Distinct().ToList();
                foreach (var item in listWechat)
                {
                    SendSignWeChat(item, message, messageRedis.BillCode, messageRedis.DispManPhone, messageRedis.Scan);
                }
            }
        }

        /// <summary>
        /// 发送第三方派件微信提醒 自取
        /// </summary>
        /// <param name="messageRedis"></param>
        public static void OtherDispWeChat(MessageRedisEntity messageRedis)
        {
            try
            {
                string message = string.Format("尊敬的客户,您的快件{0}已由{1}代收,请尽快自取,联系电话{2}", messageRedis.BillCode, messageRedis.ReName, messageRedis.RePhone);
                List<string> subDataList = messageRedis.SubData;
                List<string> listWechat = new List<string>();
                // strArray 是类似数组 2,0,openid,0,公司id,用户id  2,0,openid,0,公司id,用户id.
                string[] array;
                foreach (var strArray in subDataList)
                {
                    array = strArray.Split(Subscriber.ValueSplit);
                    listWechat.Add(array[2]);
                }
                if (listWechat.Any())
                {
                    listWechat = listWechat.Distinct().ToList();
                    foreach (var item in listWechat)
                    {
                        SendDispWeChat(item, message, messageRedis.BillCode, messageRedis.Scan, messageRedis.DispManPhone);
                    }
                }
            }
            catch (Exception ex)
            {
                NLogHelper.Warn(ex, "发送发送第三方派件微信提醒 DispWeChat ,messageRedis=" + messageRedis.FastToJson());
                throw ex;
            }
        }

        /// <summary>
        /// 状态提醒
        /// </summary>
        /// <param name="openId"></param>
        /// <param name="billCode"></param>
        /// <param name="scan"></param>
        /// <param name="state"></param>
        /// <param name="message"></param>
        /// <param name="detailUrl"></param>
        /// <returns></returns>
        public static SendTemplateMessageResult SendBillStatusChanged(string openId, string billCode, ScanType scan, string state, string message, string detailUrl = "http://wap.kuaidi.cn")
        {
            string templateId = "7Yw1jjpW-KQIq-hIbNLobaiqW2VV6mk1sB7h6Ztff-8";   //模版id
            //string linkUrl = "http://www.kuaidi.cn";    //点击详情后跳转后的链接地址,为空则不跳转  带单号的快件跟踪查询地址
            //为模版中的各属性赋值
            var templateData = new
            {
                orderNumber = new TemplateDataItem(billCode, "#000000"),
                status = new TemplateDataItem(state, "#000000"),
                remark = new TemplateDataItem("欢迎使用 www.kuaidi.cn", "#000000"),
            };
            string linkUrl = string.Format("http://tuisong.kuaidi.cn/Track/Result?openId={0}&billCode={1}", openId, billCode);
            return SendTemplateMessage(billCode, openId, scan, templateId, "", linkUrl, templateData, message);
        }

        /// <summary>
        /// 发送派件提醒消息
        /// </summary>
        /// <param name="openId">微信OpenId</param>
        /// <param name="message">消息</param>
        /// <param name="billCode">单号</param>
        /// <param name="contact">收派员电话</param>
        public static SendTemplateMessageResult SendDispWeChat(string openId, string message, string billCode, ScanType scan, string contact)
        {
            #region 微信派件消息模板
            //{{first.DATA}} 快件单号:{{waybillNo.DATA}} 收派员电话:{{contact.DATA}} {{remark.DATA}}
            //尊敬的客户: 您有XX市寄来快件预计2小时内上门派送,如是偏远地区需加时,有疑问请联系收派员。
            //快件单号:XXXXXXXXXXXX 收派员电话:XXXXXXXXXXX
            //为客户提供一站式物流解决方案,用心打造物流超市,现推出“门到门”服务的物流普运,单公斤价格低至1元,是您寄递大、重货的新选择!详情请登录t.cn/8sVmnz2。
            #endregion
            string templateId = "aoEcxWhHkVyr9m45zIv6TmDyGdLsKmfA3hWVY4bekgo";   //模版id
            //string linkUrl = "http://www.kuaidi.cn";    //点击详情后跳转后的链接地址,为空则不跳转  带单号的快件跟踪查询地址
            //为模版中的各属性赋值
            var templateData = new
            {
                first = new TemplateDataItem(message, "#000000"),
                waybillNo = new TemplateDataItem(billCode, "#000000"),
                contact = new TemplateDataItem(contact, "#000000"),
                remark = new TemplateDataItem("欢迎使用 www.kuaidi.cn", "#000000"),
            };
            string linkUrl = string.Format("http://tuisong.kuaidi.cn/Track/Result?openId={0}&billCode={1}", openId, billCode);
            return SendTemplateMessage(billCode, openId, scan, templateId, "", linkUrl, templateData, message);
        }

        /// <summary>
        /// 发送签收提醒消息
        /// </summary>
        /// <param name="openId">微信OpenId</param>
        /// <param name="message">消息</param>
        /// <param name="dateTime">签收时间</param>
        /// <param name="billCode">单号</param>
        public static SendTemplateMessageResult SendSignWeChat(string openId, string message, string dateTime, string billCode, ScanType scan)
        {
            #region 微信签收消息模板
            //{{first.DATA}}
            //签收时间:{{time.DATA}}
            //快件单号:{{waybillNo.DATA}}
            //{{remark.DATA}}
            //尊敬的客户:
            //您寄往XX市已被XX签收。
            //签收时间:昨日辰时
            //快件单号:XXXXXXXXXXXX
            //致力于为客户提供一站式物流解决方案,用心打造物流超市,现推出“门到门”服务的物流普运,单公斤价格低至1元,是您寄递大、重货的新选择!详情请登录t.cn/8sVmnz2。
            #endregion
            string templateId = "Qx_K9opg_bg5XZV2BlHCKXcrUiQbfek8pqCqdYGSUK4";   //模版id
            //string linkUrl = "http://www.kuaidi.cn";    //点击详情后跳转后的链接地址,为空则不跳转  带单号的快件跟踪查询地址
            //为模版中的各属性赋值
            var templateData = new
            {
                first = new TemplateDataItem(message, "#000000"),
                time = new TemplateDataItem(dateTime, "#000000"),
                waybillNo = new TemplateDataItem(billCode, "#000000"),
                remark = new TemplateDataItem("欢迎使用 www.kuaidi.cn", "#000000"),
            };
            string linkUrl = string.Format("http://tuisong.kuaidi.cn/Track/Result?openId={0}&billCode={1}", openId, billCode);
            return SendTemplateMessage(billCode, openId, scan, templateId, "", linkUrl, templateData, message);
        }

        /// <summary>
        /// 模板消息接口
        /// </summary>
        /// <param name="billCode">单号</param>
        /// <param name="openId">关注者OpenId</param>
        /// <param name="scanType">扫描类型</param>
        /// <param name="templateId">模板ID</param>
        /// <param name="topcolor">标题颜色</param>
        /// <param name="linkUrl">跳转地址</param>
        /// <param name="templateData">数据</param>
        /// <param name="message">消息内容</param>
        /// <param name="timeOut">代理请求超时时间(毫秒)</param>
        /// <returns></returns>
        public static SendTemplateMessageResult SendTemplateMessage(string billCode, string openId, ScanType scanType, string templateId, string topcolor, string linkUrl, object templateData, string message, int timeOut = Config.TIME_OUT)
        {
            SendTemplateMessageResult sendResult = null;
            string sendError = string.Empty;
            string accessToken = WechatPublicBindManager.GetAccessToken();
            sendResult = TemplateApi.SendTemplateMessage(accessToken, openId, templateId, topcolor, linkUrl, templateData, timeOut);
            int status;
            string statusMessage;
            //if (sendResult != null && sendResult.errcode.ToString() == "请求成功")
            if (sendResult != null && sendResult.errcode == 0)
            {
                status = 1;
                statusMessage = sendResult.errcode + ",msgid=" + sendResult.msgid;
            }
            else
            {
                // 发送失败记录
                status = 0;
                statusMessage = sendResult == null ? "微信推送失败,可能已经不再关注" + sendError : sendResult.errcode + ",msgid=" + sendResult.msgid;
            }
            // 更新数据库及统计日志
            Subscriber.UpdateSubscriberInfo(billCode, openId, PushType.WeChat, scanType, message, status, statusMessage);
            return sendResult;
        }
    }

 至此结束,项目代码结构如下。

原文地址:https://www.cnblogs.com/lonelyxmas/p/10767395.html

时间: 2024-10-11 03:40:53

快递企业如何完成运单订阅消息的推送的相关文章

nodejs+socketio+redis实现前端消息实时推送

nodejs+socketio+redis实现前端消息实时推送 1. 后端部分 发送redis消息 可以参考此篇实现(直接使用Jedis即可) http://www.cnblogs.com/binyue/p/4763352.html 2.后端部分: 接收redis消息 var redis; if(process.argv.length <= 2){ redis = require('redis').createClient(); }else{ redis = require('redis').c

基于HTTP协议之WEB消息实时推送技术原理及实现

很早就想写一些关于网页消息实时推送技术方面的文章,但是由于最近实在忙,没有时间去写文章.本文主要讲解基于 HTTP1.1 协议的 WEB 推送的技术原理及实现.本人曾经在工作的时候也有做过一些用到网页消息实时推送的项目,但是当时实现的都不是很完美,甚至有时候是通过 Ajax 轮训的方式实现.在网上也找过不少的资料,真正说到点子上的几乎没有,很多文章大都是长篇大论,说了一些新有名字,什么“HTTP 长连接”,“实时推送”,“Comet 长连接推送技术”等.但真正提到如何实现实时推送的文章倒是没有看

调用APP标准消息接口推送信息http协议

调用协议:Http协议 调用方式:CRM中新分派线索(实时)或者线索未及时更新(定时,每天执行一次)时,调用APP标准消息接口推送信息,成功后返回标记已通知过. 接口调用方法如下: { "apikey":"xxxsadfsd", "identifier":"com.xx.xx",//向移动应用平台申请 "receiverType":"NAME", "receiverValue&

浏览器消息自动推送研究

首先说明,这篇博文不是科普讲解的,而是立flag研究的,是关于浏览器消息自动推送,就是下面这个玩意: 最近常常在浏览器看到这样的消息推送,还有QQ.com的推送,现在我对这个不了解,不知道叫消息自动推送对不对,这个时chrome浏览器的截图,出现在右下角,其他浏览器的样式可能有些微差别. websocket通信?浏览器广告推送?html5自动更新?灵异事件? ----------------------------我是研究的结果华丽的分割线----------------------------

dwr3实现消息精确推送详细步骤

最近项目中需要用到推送消息,找了很久终于找到一篇不错的文章,方便以后查看就转载了,也分享给大家,希望能帮到有需要的人. 第一.在项目中引入dwr.jar,然后在web.xml中进行配置,配置如下: <servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class> org.directwebremoting.servlet.DwrServlet </servlet-class> &

Spring+Websocket实现消息的推送

Websocet服务端实现 WebSocketConfig.java @Configuration @EnableWebMvc @EnableWebSocket public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry re

借助云开发实现小程序订阅消息(模板消息)推送功能

之前的模板消息推送,将在2020年1月10日下线,所以我们不得不使用订阅消息了. 我们先来看下订阅消息的官方简介.接下来我们就来借助云开发,来快速实现小程序消息推送的功能. 一:获取模板 ID 这一步和我们之前的模板消息推送是一样的,也是先添加模板,然后拿到模板id首先是开通订阅消息功能,很简单,如下图由于长期性订阅消息,目前仅向政务民生.医疗.交通.金融.教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务.仅就线下公共服务这一点,长期性订阅消息就和大部分开发者无缘了.所以我们这里只能

rocketmq消息重复推送的问题

最近,在公司的测试环境,遇到个问题,每次重启应用重启后,原来消费过的消息又被重复推送了一遍,消费者和生产者代码如下: package com.tf56.queue.client; import java.util.concurrent.TimeUnit; import com.alibaba.rocketmq.client.exception.MQBrokerException; import com.alibaba.rocketmq.client.exception.MQClientExcep

#研发中间件介绍#异步消息可靠推送Notify

郑昀 基于朱传志的设计文档 最后更新于2014/11/11 关键词: 异步消息 .订阅者集群.可伸缩.Push模式.Pull模式 本文档适用人员:研发 电商系统为什么需要 NotifyServer? 如子柳所说,电商系统『 需要两种中间件系统,一种是实时调用的中间件(淘宝的HSF,高性能服务框架).一种是异步消息通知的中间件(淘宝的Notify)』.那么用传统的 ActiveMQ/RabbitMQ 来实现 异步消息发布和订阅 不行吗? 2013年之前我们确实用的是 ActiveMQ,当然主要是订