又封周末,閑暇無聊,隨手寫了一個關於微信公眾號服務的中間件,基於.NetCore2.1。服務類庫采用.Net Standard2.0,兼容.net 4.6.1。
整體思路是,設計一個中間件,提供微信消息推送服務。目前實現了,接收微信消息推送后,根據消息類型,對事件消息和被動接收消息分別進行了處理。
在中間件和服務之間,創建一個服務提供類,擁有提供消息的處理邏輯,開發者,可以實現服務提供接口,完成自己的邏輯。下面,讓我們看看關於中間件的代碼設計:
這里,我新建了一個名為WeiXinMiddleware的類,代碼如下:
/// <summary> /// <![CDATA[微信中間件]]> /// </summary> public class WeiXinMiddleware { /// <summary> /// /// </summary> private RequestDelegate Next = null; /// <summary> /// <![CDATA[配置]]> /// </summary> public IConfiguration Configuration { get; } /// <summary> /// <![CDATA[中間件配置信息]]> /// </summary> public OAuth.WeiXinServerOptions ServerOptions { get; set; } /// <summary> /// <![CDATA[構造]]> /// </summary> /// <param name="requestDelegate"></param> /// <param name="configuration"></param> public WeiXinMiddleware(RequestDelegate requestDelegate, IConfiguration configuration, OAuth.WeiXinServerOptions serverOptions) { Next = requestDelegate; Configuration = configuration; ServerOptions = serverOptions; } /// <summary> /// <![CDATA[調用]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task Invoke(HttpContext context) {if (context.Request.Path == ServerOptions.NotifyPath) { //微信服務 if (ServerOptions.WeiXinServerProvider == null) ServerOptions.WeiXinServerProvider = (OAuth.IWeiXinServerProvider)context.RequestServices.GetService(typeof(OAuth.IWeiXinServerProvider)); await ServerOptions.WeiXinServerProvider.Run(context, Configuration); return; } await Next.Invoke(context); } }
代碼其實很簡單,就是在類內部定義一個Invoke任務,再聲明一個Next屬性,用於請求的下一步處理委托。在中間件的構造函數中,進行了注入,其中有一個
WeiXinServerOptions 類,它便是定義中間件所需的配置信息,也是對外提供的接口,讓我們看看具體的代碼:
/// <summary> /// /// </summary> public class WeiXinServerOptions { /// <summary> ///<![CDATA[微信通知地址]]> /// </summary> public PathString NotifyPath { get; set; } /// <summary> /// /// </summary> private IWeiXinServerProvider _ServerProvider = null; /// <summary> /// <![CDATA[微信服務提供程序]]> /// </summary> public IWeiXinServerProvider WeiXinServerProvider { get { return _ServerProvider; } set { _ServerProvider = value; _ServerProvider.ServerOptions = this; } } /// <summary> /// <![CDATA[當接收到消息時]]> /// </summary> public Func<HttpContext, Task> OnRecieveAsync { get; set; } /// <summary> /// <![CDATA[掃描事件]]> /// </summary> public Func<WeiXinContext, Task> OnScanAsync { get; set; } /// <summary> /// <![CDATA[關注事件]]> /// </summary> public Func<WeiXinContext, Task> OnSubscribeAsync { get; set; } /// <summary> /// <![CDATA[取消關注]]> /// </summary> public Func<WeiXinContext, Task> OnUnsubscribeAsync { get; set; } /// <summary> /// <![CDATA[菜單點擊事件]]> /// </summary> public Func<WeiXinContext, Task> OnClickAsync { get; set; } /// <summary> /// <![CDATA[點擊鏈接]]> /// </summary> public Func<WeiXinContext, Task> OnViewAsync { get; set; } /// <summary> /// <![CDATA[上報地理位置]]> /// </summary> public Func<WeiXinContext, Task> OnLocationAsync { get; set; } /// <summary> /// <![CDATA[被動接收普通消息]]> /// </summary> public Func<HttpContext, Task> OnRecieveMessageAsync { get; set; } }
這個類中,定義了中間件要攔截處理的URL,以及時間消息的處理委托,有了這些委托,我們就可以很靈活的實現在接收到微信推送消息后的邏輯處理。
這個類中,還定義了一個WeiXinServerProvider屬性,它是接口IWeiXinServerProvider的派生,讓我們看看它定義的成員吧!
public interface IWeiXinServerProvider { /// <summary> /// /// </summary> OAuth.WeiXinServerOptions ServerOptions { get; set; } /// <summary> /// /// </summary> /// <param name="context"></param> /// <param name="configuration"></param> /// <param name="serverOptions"></param> /// <returns></returns> Task Run(HttpContext context, IConfiguration configuration); }
很簡單吧,一個屬性,一個運行任務的函數。
上面幾個類是我服務的核心,下面我又創建了2個擴展類,分別為添加中間件和IOC注入服務。
/// <summary> /// <![CDATA[微信中間件擴展]]> /// </summary> public static class WeiXinMiddlewareExtensions { /// <summary> /// <![CDATA[]]> /// </summary> /// <param name="app"></param> /// <param name="serverOptions"></param> public static void UseWeiXinServer(this IApplicationBuilder app, OAuth.WeiXinServerOptions serverOptions) { app.UseMiddleware<Middleware.WeiXinMiddleware>(serverOptions); } }
下面是IOC注入的擴展方法:
/// <summary> /// /// </summary> public static class WeiXinServiceCollectionExtensions { /// <summary> /// /// </summary> /// <param name="services"></param> public static void AddWeiXinServer(this IServiceCollection services) { services.AddSingleton(typeof(OAuth.IWeiXinServerProvider), typeof(OAuth.WeiXinServer));//單例:IOC注冊服務類型 } }
完成以上代碼后,最后讓我們再Start類中,進行服務的配置。
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddWeiXinServer();//IOC注冊服務類型 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } /// <summary> /// /// </summary> /// <param name="app"></param> /// <param name="env"></param> public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } //使用微信中間件 app.UseWeiXinServer(new OAuth.WeiXinServerOptions() { NotifyPath = new PathString("/OAuth/WeiXin"), //WeiXinServerProvider = new OAuth.WeiXinServer(),//此處也可也手動設置,默認通過IOC容器創建WeiXinServer實例。 OnScanAsync = (context) => { return Task.Delay(0); }, OnClickAsync = (context) => { return Task.Delay(0); }, OnSubscribeAsync = (context) => { return Task.Delay(0); }, OnUnsubscribeAsync = (context) => { return Task.Delay(0); }, OnViewAsync = (context) => { return Task.Delay(0); }, OnRecieveMessageAsync = (context) => { return Task.Delay(0); }, }); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } }
讓我們再看看WeiXinServer類的定義:
/// <summary> /// <![CDATA[微信服務]]> /// </summary> public class WeiXinServer : IWeiXinServerProvider { /// <summary> /// <![CDATA[服務選項]]> /// </summary> public OAuth.WeiXinServerOptions ServerOptions { get; set; } /// <summary> /// /// </summary> public WeiXinServer() { } /// <summary> /// <![CDATA[運行服務]]> /// </summary> /// <param name="context"></param> /// <param name="configuration"></param> /// <param name="serverOptions"></param> /// <returns></returns> public async Task Run(HttpContext context, IConfiguration configuration) { #region 1、驗證簽名 if (context.Request.Method.ToUpper() == "GET") { context.Response.ContentType = "text/plain;charset=utf-8"; context.Response.StatusCode = 200; //1、驗證簽名 if (WeiXin.Sdk.Common.Util.CheckSignature(context.Request.Query["nonce"], context.Request.Query["timestamp"], context.Request.Query["signature"], configuration.GetSection("WeiXinOAuth")["Token"])) { await context.Response.WriteAsync(context.Request.Query["echostr"]); return; } await context.Response.WriteAsync("無效簽名!"); return; } #endregion 1、驗證簽名 #region 2、接收微信消息 await OnRecieve(context);//接收消息 #endregion 2、接收微信消息 } #region 虛方法 /// <summary> /// <![CDATA[虛方法,接收消息后處理]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnRecieve(HttpContext context) { if (ServerOptions.OnRecieveAsync != null) return ServerOptions.OnRecieveAsync(context); string strRecieveBody = null;//接收消息 using (System.IO.StreamReader streamReader = new System.IO.StreamReader(context.Request.Body)) { strRecieveBody = streamReader.ReadToEndAsync().GetAwaiter().GetResult(); } //序列化 WeiXin.Sdk.Common.Serialization.XmlSerializer xmlSerializer = new WeiXin.Sdk.Common.Serialization.XmlSerializer(typeof(WeiXin.Sdk.Domain.Messages.Message)); var recieve = (WeiXin.Sdk.Domain.Messages.Message)xmlSerializer.Deserialize(strRecieveBody); //事件消息 if (recieve.MsgType == WeiXin.Sdk.Common.Constants.SystemConstants.MSG_TYPE.EVENT) { var weiXinContext = new WeiXinContext(recieve, context);
var weiXinContext = new WeiXinContext(recieve, context);
var actionName = recieve.Event.ToLower();
actionName = actionName.First().ToString().ToUpper() + actionName.Substring(1);
var action = this.GetType().GetMethod($"On{actionName}");
if (action != null) return (Task)action.Invoke(this, new object[] { weiXinContext });
} //被動接收消息 else { return OnRecieveMessage(context); } return Task.Delay(0); } /// <summary> /// <![CDATA[被動接收消息]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnRecieveMessage(HttpContext context) { if (ServerOptions.OnRecieveMessageAsync != null) return ServerOptions.OnRecieveMessageAsync(context); return Task.Delay(0); } /// <summary> /// <![CDATA[掃描事件]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnScan(WeiXinContext context) { if (ServerOptions.OnScanAsync != null) return ServerOptions.OnScanAsync(context); return Task.Delay(0); } /// <summary> /// <![CDATA[關注事件]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnSubscribe(WeiXinContext context) { if (ServerOptions.OnSubscribeAsync != null) return ServerOptions.OnSubscribeAsync(context); return Task.Delay(0); } /// <summary> /// <![CDATA[取消關注]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnUnsubscribe(WeiXinContext context) { if (ServerOptions.OnUnsubscribeAsync != null) return ServerOptions.OnUnsubscribeAsync(context); return Task.Delay(0); } /// <summary> /// <![CDATA[菜單點擊]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnClick(WeiXinContext context) { if (ServerOptions.OnClickAsync != null) return ServerOptions.OnClickAsync(context); return Task.Delay(0); } /// <summary> /// <![CDATA[點擊菜單鏈接]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnView(WeiXinContext context) { if (ServerOptions.OnViewAsync != null) return ServerOptions.OnViewAsync(context); return Task.Delay(0); } /// <summary> /// <![CDATA[上報地理位置]]> /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual Task OnLocation(WeiXinContext context) { if (ServerOptions.OnLocationAsync != null) return ServerOptions.OnLocationAsync(context); return Task.Delay(0); } #endregion }
WeiXinServer類中還定義了時間消息的相關的虛方法,虛方法中,調用Options配置中定義的委托,這樣,開發者一方面可以通過繼承WeiXinServer或IWeiXinServerProvider接口,或通過設置Options屬性,來靈活運用,開發者可根據自身需求,完成
對應業務邏輯即可。有了這些設計,我們可以輕松配置和完成微信消息的處理。
以上內容的全部代碼,可以通過訪問https://gitee.com/lichaoqiang/weixinmd 獲取,不足之處,還望不吝賜教。