微信号:iDotNet

介绍:专注分享 .NET 相关技术文章、教程和工具.有时也会涉及到IT职场相关的一些东西,或者来点幽默趣文.

ASP.NET Core 微信服务中间件-.NET Core 2.1

2018-09-14 11:45 DotNet

(点击上方蓝字,可快速关注我们)


来源:李朝强 

cnblogs.com/ibeisha/p/weixinServer.html


写了一个关于微信公众号服务的中间件,基于.NetCore2.1。服务类库采用.NET Standard 2.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 获取,不足之处,还望不吝赐教。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

 
DotNet 更多文章 .NET版Web后台快速开发框架 Coldairarrow ASP.NET Core 2.0 使用Autofac实现IOC依赖注入 ElasticSearch入门 附.NET Core例子 .NET 架构篇:实用中小型公司支付中心设计 .NET Core实践之短信服务-Sikiro.SMS.Api的实现
猜您喜欢 App工程结构搭建:几种常见Android代码架构分析 分享中文书:Openshift和网络安全区域:共存方法 「前端开发者的 Docker 之旅」用 Docker 搭建 Node Express 应用 大数据时代,数据管理必备12条铁则 eoe武汉开放日——移动开发之SDK优化文字实录