经常网购的朋友,会实时收到运单状态的提醒信息,这些提醒信息包括微信推送,短信推送,邮件推送,支付宝生活窗推送,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