快遞企業如何完成運單訂閱消息的推送

原文: 快遞企業如何完成運單訂閱消息的推送

  常常網購的朋友,會實時收到運單狀態的提醒信息,這些提醒信息包括微信推送,短信推送,郵件推送,支付寶生活窗推送,QQ推送等,信息內容主要包括快件到哪裏,簽收等信息的提醒,這些友好的提醒信息會極大的加強購物者的體驗。html

  筆者目前正在一家快遞企業作這類消費消息的推送功能開發(大部分快遞企業都有實如今客戶寄完快件後能夠主動接收到快遞企業的運單狀態推送信息),對這部分有一些體會,現分享給你們(大部分功能可能只能經過代碼才方便體現出來)。redis

 

  訂閱和推送的流程圖數據庫

 

1、訂閱功能:提供微信、短信、郵件等的訂閱服務,用戶在下完單之後系統自動完成運單狀態的訂閱功能

 訂閱功能調試數組

 

  訂閱實現的原理:實現訂閱主要是把當前的單號和訂閱者的關聯信息存儲到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,
    }

  訂閱功能的核心代碼,微信,短信,郵件等的訂閱最終調用的方法ide

        /// <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關聯存儲起來,若是用戶須要郵件或手機短信推送,在下單時只要完善對應信息便可。
ui

 

2、消息隊列分發:也就是訂單狀態發生變化後,將當前最新的狀態向訂閱者的微信、手機或郵件等的推送,目前我使用消息隊列來進行運單狀態的消費推送服務,根據運單的不一樣狀態,消息隊列分爲收件、發件、派件、到件、簽收隊列。spa

  因爲推送過程(發生微信消息、短息消息、郵件消息)都是網絡耗時的一些操做,爲了保證消息隊列服務的穩定,首先將待發送的信息按照訂閱方式存儲到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;
        }
    }

 

 至此結束,項目代碼結構以下。

 

相關文章
相關標籤/搜索