基於.NetCore2.1。服務類庫采用.Net Standard2.0,兼容.net 4.6.1消息推送服務
https://www.cnblogs.com/ibeisha/p/weixinServer.html
關於微信公眾號服務的中間件,基於.NetCore2.1。服務類庫采用.Net Standard2.0,兼容.net 4.6.1。
整體思路是,設計一個中間件,提供微信消息推送服務。目前實現了,接收微信消息推送后,根據消息類型,對事件消息和被動接收消息分別進行了處理。
在中間件和服務之間,創建一個服務提供類,擁有提供消息的處理邏輯,開發者,可以實現服務提供接口,完成自己的邏輯。下面,讓我們看看關於中間件的代碼設計:
這里,我新建了一個名為WeiXinMiddleware的類,代碼如下:
復制代碼
///
/// 微信中間件
///
public class WeiXinMiddleware
{
///
///
///
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 類,它便是定義中間件所需的配置信息,也是對外提供的接口,讓我們看看具體的代碼:
復制代碼
///
///
///
public class WeiXinServerOptions
{
///
///微信通知地址
///
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注入服務。
復制代碼
///
/// 微信中間件擴展
///
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注入的擴展方法:
復制代碼
///
///
///
public static class WeiXinServiceCollectionExtensions
{
///
///
///
///
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類的定義:
復制代碼
///
/// 微信服務
///
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 獲取,不足之處,還望不吝賜教。