.Net Core 实现阿里云MNS + 简单封装

.Net Core 实现阿里云MNS + 简单封装

前言

在实际的开发应用场景会出现很多需要事件通知的场景,例如用户下单30分钟后未付款,这时候需要系统自动关闭订单等操作,这个时候不可能在后台开启一个Job去一直查询监听数据,这个时候就需要事件推送 事件订阅来完成关闭订单的操作,事件相关的中间件有很多例如RabbitMQ,ZeroMQ,RocketMQ等等等,但是相较于小型初创企业自己去搭建一个消息服务在自己去维护,笔者觉的成本过于庞大没有专业人员的运维,实际上线后可能稳定性和性能都不尽如人意,这个时候可以考虑看看各大云厂商的解决方案。

准备

本次采用的环境是 .Net Core 1.1版本

本次消息服务采用的是阿里云MNS

实践

首先根据阿里云的MNS Api 稳定我们要把相关的消息服务接口代码进行实现 如下

  1  /// <summary>
  2     /// 阿里云MNS消息服务
  3     /// </summary>
  4     public class MNSService : IMNSService
  5     {
  6         private static HttpClient client;
  7         private static string Url = @"https://xxxxxxx.mns.cn-hangzhou.aliyuncs.com/";
  8         private static string AccessKeyId;
  9         private static string AccessKeySecret;
 10         private static string Host = Url.StartsWith("http://") ? Url.Substring(7) : Url;
 11         private static string Version = "2015-06-06";
 12 
 13         public MNSService(string url,string accessKeyId,string accessKeySecret)
 14         {
 15             Url = url;
 16             AccessKeyId = accessKeyId;
 17             AccessKeySecret = accessKeySecret;
 18         }
 19 
 20         /// <summary>
 21         /// 删除消息
 22         /// </summary>
 23         /// <typeparam name="T"></typeparam>
 24         /// <param name="receipthandle"></param>
 25         /// <returns></returns>
 26         public async Task DeleteMessageAsync<T>(string receipthandle)
 27         {
 28             client = new HttpClient();
 29             var queueName = typeof(T).Name;
 30             client.DefaultRequestHeaders.Clear();
 31             Dictionary<string, string> headers = new Dictionary<string, string>();
 32             headers.Add("Host", Host);
 33             headers.Add("Date", DateTime.Now.ToUniversalTime().ToString("r"));
 34             headers.Add("x-mns-version", Version);
 35             string url = string.Format("{0}/{1}", queueName, "messages");
 36             headers.Add("Authorization", this.Authorization("DELETE", headers, string.Format("{0}", "/queues/" + queueName + "/messages?ReceiptHandle=" + receipthandle)));
 37 
 38             foreach (var kv in headers)
 39             {
 40                 if (kv.Key != "Content-Type" && kv.Key != "Host")
 41                     client.DefaultRequestHeaders.Add(kv.Key, kv.Value);
 42             }
 43             var res = await client.DeleteAsync(Url + string.Format("queues/{0}/{1}?ReceiptHandle=" + receipthandle, queueName, "messages"));
 44             if (res.StatusCode == System.Net.HttpStatusCode.NoContent)
 45             {
 46                 return;
 47             }
 48         }
 49 
 50         /// <summary>
 51         /// 消费消息
 52         /// </summary>
 53         /// <typeparam name="T">返回消息模型</typeparam>
 54         /// <param name="waitseconds">等待时间</param>
 55         /// <returns></returns>
 56         public async Task<Tuple<T, string>> ReceiveMessageAsync<T>(int waitseconds)
 57         {
 58             client = new HttpClient();
 59             var queueName = typeof(T).Name;
 60             Dictionary<string, string> headers = new Dictionary<string, string>();
 61             headers.Add("Host", Host);
 62             headers.Add("Date", DateTime.Now.ToUniversalTime().ToString("r"));
 63             headers.Add("x-mns-version", Version);
 64             string url = string.Format("{0}/{1}", queueName, "messages");
 65             headers.Add("Authorization", this.Authorization("GET", headers, string.Format("{0}", "/queues/" + queueName + "/messages?waitseconds=" + waitseconds)));
 66 
 67             foreach (var kv in headers)
 68             {
 69                 if (kv.Key != "Content-Type" && kv.Key != "Host")
 70                     client.DefaultRequestHeaders.Add(kv.Key, kv.Value);
 71             }
 72             var res = await client.GetAsync(Url + string.Format("queues/{0}/{1}?waitseconds=" + waitseconds, queueName, "messages"));
 73             if (res.StatusCode == System.Net.HttpStatusCode.OK)
 74             {
 75                 var contentText = await res.Content.ReadAsStringAsync();
 76                 var dic = contentText.GetInfoFromXml();
 77                 var ReceiptHandle = dic["ReceiptHandle"];
 78                 byte[] bpath = Convert.FromBase64String(dic["MessageBody"]);
 79                 var bodyjson = Encoding.UTF8.GetString(bpath);
 80                 var result = JsonConvert.DeserializeObject<T>(bodyjson);
 81                 return new Tuple<T, string>(result, ReceiptHandle);
 82             }
 83             else
 84             {
 85                 return new Tuple<T, string>(default(T), "");
 86             }
 87         }
 88 
 89         /// <summary>
 90         /// 发送消息
 91         /// </summary>
 92         /// <param name="bodyjson">消息内容</param>
 93         /// <returns></returns>
 94         public async Task SendMessageAsync<T>(string bodyjson)
 95         {
 96             client = new HttpClient();
 97             var queueName = typeof(T).Name;
 98             Dictionary<string, string> headers = new Dictionary<string, string>();
 99             headers.Add("Host", Host);
100             headers.Add("Date", DateTime.Now.ToUniversalTime().ToString("r"));
101             headers.Add("x-mns-version", Version);
102             headers["Content-Type"] = "text/xml";
103             string url = string.Format("{0}/{1}", queueName, "messages");
104             headers.Add("Authorization", this.Authorization("POST", headers, string.Format("{0}", "/queues/" + queueName + "/messages")));
105 
106             foreach (var kv in headers)
107             {
108                 if (kv.Key != "Content-Type" && kv.Key != "Host")
109                     client.DefaultRequestHeaders.Add(kv.Key, kv.Value);
110             }
111             client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
112             StringBuilder sb = new StringBuilder();
113             sb.Append("<Message> ");
114             sb.Append("<MessageBody>" + Convert.ToBase64String(Encoding.UTF8.GetBytes(bodyjson)) + "</MessageBody> ");
115             //sb.Append("<DelaySeconds>0</DelaySeconds> ");
116             sb.Append("<Priority>1</Priority>");
117             sb.Append("</Message>");
118             HttpContent content = new StringContent(sb.ToString());
119             content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
120             client.DefaultRequestHeaders.Connection.Add("keep-alive");
121             var res = await client.PostAsync(Url + "/" + string.Format("queues/{0}/{1}", queueName, "messages"), content);
122             if (res.StatusCode == System.Net.HttpStatusCode.Created)
123                 return;
124         }
125 
126     }

实现了阿里云的Api后 笔者发现需要自己实现消息的监听,根据阿里官方推荐监听的实现方式有两种方式官方采用的Java代码作为案例如下

第一种方式,利用定时器,如quartz, Timer, ScheduledExecutorService..., 每隔一段时间拉取一次消息。

第二种方式 ,使用while循环,如while(true)

笔者考虑第一种方式可能略显笨重,也不想依靠三方库进行实现所以采用了 while 的方案代码如下

 1     /// <summary>
 2     /// 事件总线实现
 3     /// </summary>
 4     public class EventBus : IEventBus
 5     {
 6         public IMNSService IMNSService { get; set; }
 7 
 8         public EventBus(IMNSService MNSService)
 9         {
10             IMNSService = MNSService;
11         }
12 
13         /// <summary>
14         /// 推送消息
15         /// </summary>
16         /// <typeparam name="T"></typeparam>
17         /// <param name="body">消息体</param>
18         /// <returns></returns>
19         public async Task PushAsync<T>(T body = default(T))
20         {
21             var json = JsonConvert.SerializeObject(body);
22             await IMNSService.SendMessageAsync<T>(json);
23         }
24 
25         /// <summary>
26         /// 订阅消息
27         /// </summary>
28         /// <typeparam name="T"></typeparam>
29         /// <param name="subscribeMethod">订阅的方法</param>
30         /// <returns></returns>
31         public async Task SubscribeAsync<T>(Func<T, Task> subscribeMethod)
32         {
33             while (true)
34             {
35                 try
36                 {
37                     var mq = await IMNSService.ReceiveMessageAsync<T>(10);
38                     Task.WaitAll(subscribeMethod.Invoke(mq.Item1));
39                     await IMNSService.DeleteMessageAsync<T>(mq.Item2);
40                 }
41                 catch (Exception)
42                 {
43                     //异常情况 不删除消息
44                 }
45             }
46         }
47     }

笔者在EventBus 中分别实现了,消息推送和消息订阅这里只是初步简单的封装准备逐步进行优化和扩展,实现了推送和订阅后订阅需要在Startup中进行注册因此还需要实现,订阅的注册 代码如下

 1     public static class AliYunMNSCollectionExtensions
 2     {
 3         /// <summary>
 4         /// 自动注册所有阿里云事件监听
 5         /// </summary>
 6         public static IServiceCollection AutoHandler(this IServiceCollection services, string url, string accessKeyId, string accessKeySecret, params Type[] types)
 7         {
 8             services.Scan(scan => scan.FromAssembliesOf(types)
 9               .AddClasses(classes => classes.AssignableTo(typeof(IMNSNotificationHandler<>)))
10               .AsImplementedInterfaces()
11               .WithTransientLifetime());
12 
13             services.AddTransient<IEventBus, EventBus>();
14             services.AddSingleton<IMNSService>(new MNSService(url,accessKeyId,accessKeySecret));
15             return services;
16         }
17 
18         /// <summary>
19         /// 启动需要监听的消息
20         /// </summary>
21         /// <param name="app"></param>
22         /// <returns></returns>
23         public static IApplicationBuilder AddHandler<TEvent>(this IApplicationBuilder app) where TEvent : IMNSNotification
24         {
25             var handlers = app.ApplicationServices.GetService<IEventBus>();
26             Task.Run(async () =>
27             {
28                 var eventbus = app.ApplicationServices.GetService<IMNSNotificationHandler<TEvent>>();
29                 await handlers.SubscribeAsync<TEvent>(eventbus.Handle);
30             });
31             return app;
32         }
33     }

通过AddHandler<TEvent> 注册需要监听的消息下面看一下 使用案例

第一步:创建消息模型

1     public class TestMQ : IMNSNotification
2     {
3         public string Name { get; set; }
4     }

第二步:创建订阅消息的实现

1     public class TestMQHendler : IMNSNotificationHandler<TestMQ>
2     {
3         public async Task Handle(TestMQ notification)
4         {
5             var n = notification;
6         }
7     }

第三步:注册订阅

1         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
2         {
3             loggerFactory.AddConsole(Configuration.GetSection("Logging"));
4             loggerFactory.AddDebug();
5             app.AddHandler<TestMQ>();
6             app.UseMvc();
7         }

这里需要注意的是 如果没新增一个订阅消息 都需要 app.AddHandler<XXX>(); 来注册需要订阅的消息。

思考

通过简单的对于阿里云MNS的封装可以在业务中便捷的使用消息推送和消息订阅,由于时间的关系还有很多代码没有实现比如异常机制,日志的记录,配置的扩展等,笔者将逐步完善并且和大家分享如果大家有什么更好的提意或者建议欢迎留言。

 

转载于:https://www.cnblogs.com/liufei91/p/10454777.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值