zoukankan      html  css  js  c++  java
  • Asp.Net Core微信服务中间件-.NetCore2.1

      又封周末,闲暇无聊,随手写了一个关于微信公众号服务的中间件,基于.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 获取,不足之处,还望不吝赐教。

    
    
     
  • 相关阅读:
    NFS4.1规范研究:session
    散列冲突与作为特征值的散列
    使用Select的3个注意事项
    3个学习Socket编程的简单例子:TCP Server/Client, Select
    Gdb调试多进程程序
    Usage of pmake
    诡异的bug: tcsh陷入死循环
    【转】PowerDesigner 物理数据模型(PDM) 说明
    大批量文件处理的7条建议
    OLE DB、ODBC 和 Oracle 连接池 (ADO.NET)
  • 原文地址:https://www.cnblogs.com/ibeisha/p/weixinServer.html
Copyright © 2011-2022 走看看