又封周末,闲暇无聊,随手写了一个关于微信公众号服务的中间件,基于.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. PTA 1067 Sort with Swap(0, i) (25 分)(思维)

    传送门:点我 Given any permutation of the numbers {0, 1, 2,..., N−1}, it is easy to sort them in increasin ...

  2. FloatingActionButton FAB 悬浮按钮

    FloatingActionButton简称FAB,这是一种比较美观的按钮: 1.使用前: FAB代表一个App或一个页面中最主要的操作,如果一个App的每个页面都有FAB,则通常表示该App最主要的 ...

  3. [leetcode]45. Jump Game II青蛙跳(跳到终点最小步数)

    Given an array of non-negative integers, you are initially positioned at the first index of the arra ...

  4. time模块的使用

    https://www.cnblogs.com/jimmy-share/p/10605575.html import time 一.方法汇总: time.sleep():定时函数 time.time( ...

  5. Linux的php-fpm优化心得-php-fpm进程占用内存大和不释放内存问题(转)

    原文地址:https://wzfou.com/php-fpm/ 最近发现博客的内存老是隔三差五地被“吃掉”了,登录到后台后偶尔会出卡顿的情况,一开始怀疑是Swap不够导致的,于是给VPS主机增加了几个 ...

  6. 洛谷1345 [USACO5.4]奶牛的电信Telecowmunication

    原题链接 最小割点数转换成最小割边数的模板题(不过这数据好小). 每个点拆成两个点,连一条容量为\(1\)的边,原图的边容量定为\(+\infty\),然后跑最大流即可. 这里用的是\(Dinic\) ...

  7. linux命令-crontab

    一.安装 yum install crontabs 二.基本使用 1.crontab -e:创建任务,进入编辑 格式: 基本格式 : ——————————————————— * * * * * com ...

  8. 20172325 2018-2019-2 《Java程序设计》第八周学习总结

    20172325 2018-2019-2 <Java程序设计>第八周学习总结 教材学习内容总结 一.堆 1.什么是堆? 具有两个附加属性的一个二叉树. 堆分为小顶堆和大顶堆. 最小堆:对每 ...

  9. .net core 中使用ef 访问mysql

    1.参考文档说修改项目文件添加,就得这么做,不然会报错 <ItemGroup> <DotNetCliToolReference Include="Microsoft.Ent ...

  10. RQNOJ 3 Jam的计数法

    一道模拟题,用的vector比用链表要方便很多,毕竟不需要自己写,因为是递增的,所以每一次你都要去检查最后一位加1之后有没有越界,如果没越界你就可以把他当前的字符删掉替换成他下一位的字符就可以了,如果 ...