又封周末,闲暇无聊,随手写了一个关于微信公众号服务的中间件,基于.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(); },
OnClickAsync = (context) => { return Task.Delay(); },
OnSubscribeAsync = (context) => { return Task.Delay(); },
OnUnsubscribeAsync = (context) => { return Task.Delay(); },
OnViewAsync = (context) => { return Task.Delay(); },
OnRecieveMessageAsync = (context) => { return Task.Delay(); },
}); 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 = ; //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();
} /// <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();
} /// <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();
} /// <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();
} /// <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();
} /// <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();
} /// <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();
} /// <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();
}
#endregion
}

WeiXinServer类中还定义了时间消息的相关的虚方法,虚方法中,调用Options配置中定义的委托,这样,开发者一方面可以通过继承WeiXinServer或IWeiXinServerProvider接口,或通过设置Options属性,来灵活运用,开发者可根据自身需求,完成

对应业务逻辑即可。有了这些设计,我们可以轻松配置和完成微信消息的处理。

以上内容的全部代码,可以通过访问https://gitee.com/lichaoqiang/weixinmd 获取,不足之处,还望不吝赐教。


 

Asp.Net Core微信服务中间件-.NetCore2.1的更多相关文章

  1. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  2. 在Asp.net Core中使用中间件来管理websocket

    介绍 ASP.NET Core SignalR是一个有用的库,可以简化Web应用程序中实时通信的管理.但是,我宁愿使用WebSockets,因为我想要更灵活,并且与任何WebSocket客户端兼容. ...

  3. ASP.NET Core系列:中间件

    1. 概述 ASP.NET Core中的中间件是嵌入到应用管道中用于处理请求和响应的一段代码. 2. 使用 IApplicationBuilder 创建中间件管道 2.1 匿名函数 使用Run, Ma ...

  4. ASP.NET Core静态文件中间件[1]: 搭建文件服务器

    虽然ASP.NET Core是一款"动态"的Web服务端框架,但是由它接收并处理的大部分是针对静态文件的请求,最常见的是开发Web站点使用的3种静态文件(JavaScript脚本. ...

  5. ASP.NET Core 3.1 中间件

    参考微软官方文档 : https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1 ...

  6. ASP.NET Core错误处理中间件[1]: 呈现错误信息

    NuGet包"Microsoft.AspNetCore.Diagnostics"中提供了几个与异常处理相关的中间件.当ASP.NET Core应用在处理请求过程中出现错误时,我们可 ...

  7. ASP.NET Core错误处理中间件[4]: 响应状态码页面

    StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件类似,它们都是在后续请求处理过程中"出错"的情况下利用一个错误处 ...

  8. 2、ASP.NET Core中服务的生命周期

    ASP.NET Core支持依赖注入软件设计模式,它允许在不同的组件中注入我们的服务,并且控制服务的初始化.有些服务可以在短时间内初始化,并且只能在某个特别的组件,以及请求中才能用到:而还有一些服务, ...

  9. ASP.NET Core 微服务初探[1]:服务发现之Consul

    ASP.NET Core 微服务初探[1]:服务发现之Consul   在传统单体架构中,由于应用动态性不强,不会频繁的更新和发布,也不会进行自动伸缩,我们通常将所有的服务地址都直接写在项目的配置文件 ...

随机推荐

  1. C/S和B/S架构

    1.C/S架构(Client/Server结构,熟知的客户机和服务器结构),它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统 ...

  2. Python基础-python基本语法(二)

    一.注释 分类:单行注释和多行注释 1.单行注释 单行注释以#开头,在当前行内,#后面的内容就是注释内容 2.多行注释 被两个   '''    或     ''''''    包括起来的内容就是注释 ...

  3. 【Android】异步加载布局探索

    最近在做的项目页面复杂导致布局嵌套多层,而且又使用了百分比布局(可能主要是这个原因)导致页面加载的时候主线程会被阻塞, 那要想减少主线程阻塞,一来就是简化布局,减轻LayoutInflater的负担, ...

  4. 网络编程初识和socket套接字

    网络的产生 不同机器上的程序要通信,才产生了网络:凡是涉及到倆个程序之间通讯的都需要用到网络 软件开发架构 软件开发架构的类型:应用类.web类 应用类:qq.微信.网盘.优酷这一类是属于需要安装的桌 ...

  5. Difference Among Mercedes Star Diagnostic Tool MB Star C3 C4 C5 C6

    Mercedes Star Diagnostic Tool newly update to MB Star C6.There are many star diangostic tool in the ...

  6. 20. pt-show-grants

    pt-show-grants -h 192.168.100.101 -P 3306 -u admin -p admin 也可以delete,revoke,flush privileges , 用的不多 ...

  7. ubuntu中给python3安装opencv

    一.安装相关工具包******注意:以下3,4,5,6为可选项,根据需求安装******1.更新库 sudo apt-get update sudo apt-get upgrade 2.安装从源码构建 ...

  8. JSP :使用<%@include%>报Duplicate local variable path 错误

    今天在做商城页面,遇到问题: <%@include file="menu.jsp" %> 错误提示: Multiple annotations found at thi ...

  9. MFC设置单文档保存格式以及标题

    在使用MFC编写单文档程序时,有时候需要将编辑的内容序列化为文件,使该文件可以直接以自己的程序打开,这时候需要在保存时将文件后缀改为我们想要的格式. 步骤 打开String Table,找到IDR_M ...

  10. w9 Ansible批量管理与维护

    Ansible是2013年推出的一种通用自动化工具,可用于配置管理或工作流程自动化.配置管理是一种“基础架构代码”实践,它将事物编码,例如应该在系统上安装什么包和版本,或者应该运行什么守护进程.工作流 ...