• 最近的项目系之3——core3.0整合Senparc


    1、前言

      既然是.net下微信开发,自然少不了Senparc,可以说这个框架的存在, 至少节省了微信相关工作量的80%。事实上,项目开始前,还纠结了下是Java还是core,之所以最终选择core,除了情怀外,更重要的便是这个微信开发框架的存在。本项目的整合方式,极大程度上参考了Senparc官方的示例,官方示例可以说很全面、详细了。

    2、整合方式

    1)增加Senparc配置节

    appsettings.json中添加如下配置节:

     "SenparcSetting": {
        "IsDebug": true,
        "DefaultCacheNamespace": "Fuck"
        //分布式缓存
        //"Cache_Redis_Configuration": "#{Cache_Redis_Configuration}#", 
        //"Cache_Memcached_Configuration": "#{Cache_Memcached_Configuration}#", 
        //"SenparcUnionAgentKey": "#{SenparcUnionAgentKey}#" 
      },
      "SenparcWeixinSetting": {
        "IsDebug": true,
        "Token": "Fuck",
        "EncodingAESKey": "FuckKey",
        "WeixinAppId": "FuckAppId",
        "WeixinAppSecret": "FuckAppSecret"
      },

    SenparcSetting部分是Senparc底层的通用配置,目前我项目中暂未用到,如果用到则对应配置,如缓存的命名空间,用来防止多应用可能的缓存key冲突,分布式缓存连接等。

    SenparcWeixinSetting是公众号相关的配置,Token、EncodingAESKey、WeixinAppId、WeixinAppSecret均分别对应公众号后台的账户信息,不多赘述。生产环境中,记得把上述IsDebug配置为false,减少调试信息及提高性能。

    2) 微信消息处理器

      增加自定义消息处理器,继承至MessageHandler<DefaultMpMessageContext>:

    public class CustomMessageHandler : MessageHandler<DefaultMpMessageContext>
        {
            public CustomMessageHandler(Stream inputStream, PostModel postModel, int maxRecordCount = 0, bool onlyAllowEcryptMessage = false)
                : base(inputStream, postModel, maxRecordCount, onlyAllowEcryptMessage)
            {
                OnlyAllowEcryptMessage = true;
                //在指定条件下,不使用消息去重
                base.OmitRepeatedMessageFunc = requestMessage =>
                {
                    var textRequestMessage = requestMessage as RequestMessageText;
                    if (textRequestMessage != null && textRequestMessage.Content == "容错")
                    {
                        return false;
                    }
                    return true;
                };
            }
    
            public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
            {
                var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
                responseMessage.Content = "您好,欢迎关注XXXX!";
    
                return responseMessage;
            }
        }

      

      重写的DefaultResponseMessage方法表示,系统收到微信用户收到的任何消息时,都自动回复"您好,欢迎关注XXXX!"的文本消息。MessageHandler<DefaultMpMessageContext>中可以重载的方法很多,主要是响应微信终端动作的一系列方法,比如用户发送文本、用户点击链接、用户发送图片、发送位置等,如果你需要处理对应事件,那就重载对应方法,我这里偷懒了,搞了个所有类型消息默认回复。

    3)系统与微信通信

      增加控制器,如下:

        [AllowAnonymous]
        public class WeixinController : Controller
        {
            private readonly IWebHostEnvironment _env;
            private readonly SenparcWeixinSetting _weixinSetting;
    
            public WeixinController(IWebHostEnvironment env,
                IOptions<SenparcWeixinSetting> weixinSetting)
            {
                _env = env;
                _weixinSetting = weixinSetting.Value;
            }
    
            [HttpGet]
            [ActionName("Index")]
            public Task<ActionResult> Get(string signature, string timestamp, string nonce, string echostr)
            {
                return Task.Factory.StartNew(() =>
                {
                    if (CheckSignature.Check(signature, timestamp, nonce, _weixinSetting.Token))
                    {
                        return echostr; //返回随机字符串则表示验证通过
                    }
                    else
                    {
                        return $"failed:{signature},{CheckSignature.GetSignature(timestamp, nonce, _weixinSetting.Token)}。如果你在浏览器中看到这句话,说明此地址可以被作为微信公众账号后台的Url,请注意保持Token一致。";
                    }
                })
                    .ContinueWith<ActionResult>(task => Content(task.Result));
            }
    
            /// <summary>
            /// 最简化的处理流程
            /// </summary>
            [HttpPost]
            [ActionName("Index")]
            public async Task<ActionResult> Post(PostModel postModel)
            {
                if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, _weixinSetting.Token))
                {
                    return new WeixinResult("参数错误!");
                }
    
                postModel.Token = _weixinSetting.Token;
                postModel.EncodingAESKey = _weixinSetting.EncodingAESKey;
                postModel.AppId = _weixinSetting.WeixinAppId;
    
                var cancellationToken = new CancellationToken();
    
                var messageHandler = new CustomMessageHandler(Request.GetRequestMemoryStream(), postModel, 10)
                {
                    DefaultMessageHandlerAsyncEvent = DefaultMessageHandlerAsyncEvent.SelfSynicMethod
                };
                messageHandler.GlobalMessageContext.ExpireMinutes = 3;
    
                //messageHandler.SaveRequestMessageLog();
                await messageHandler.ExecuteAsync(cancellationToken);
                //messageHandler.SaveResponseMessageLog();
    
                return new FixWeixinBugWeixinResult(messageHandler);
            }
    
            [HttpPost]
            public ActionResult CreateMenu()
            {
                var menuFileInfo = _env.ContentRootFileProvider.GetFileInfo("menu.json");
                using (var stream = menuFileInfo.CreateReadStream())
                {
                    using (StreamReader streamReader = new StreamReader(stream))
                    {
                        var menuContent = streamReader.ReadToEnd();
                        MenuFull_ButtonGroup buttonGroup = JsonSerializer.Deserialize<MenuFull_ButtonGroup>(menuContent);
    
                        var tokenResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(_weixinSetting.WeixinAppId, _weixinSetting.WeixinAppSecret);
                        if (tokenResult.errcode != ReturnCode.请求成功)
                        {
                            return Json(tokenResult);
                        }
    
                        var menuResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.CreateMenu(tokenResult.access_token, buttonGroup);
                        if (menuResult.errcode != ReturnCode.请求成功)
                        {
                            return Json(menuResult);
                        }
    
                        return Json("设置成功");
                    }
                }
            }
    
            /// <summary>
            /// 获取菜单接口
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public ActionResult GetMenu()
            {
                var tokenResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(_weixinSetting.WeixinAppId, _weixinSetting.WeixinAppSecret);
                if (tokenResult.errcode != ReturnCode.请求成功)
                {
                    return Json(tokenResult);
                }
    
                var menuResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetMenu(tokenResult.access_token);
    
                return Json(menuResult);
            }
        }

      构造函数中,注入微信相关配置SenparcWeixinSetting,get方法,用来响应微信官方的URL校验,注意该方法公布出去的reset地址需要跟公众号后台配置的token校验地址一致。关于微信的token校验,相比前几年的一个变化是,开发者需要在域名对应根路径下放置一个微信后台提供下载的TXT文件,听起来绕是吧,那我往简单说,就是http://yourdomain/xxxx.txt需要能访问到公众号后台下载的那个xxxx.txt。可以根据具体部署情况灵活处理此要求,比如可以在反向代理层,也可以在应用中去处理,比如我这儿就是直接放在系统应用中处理,具体来说,如果在core中引用了UseStaticfile中间件,则core默认把wwwroot作为域名根目录公布出去,我们的前端文件就是这么被公布出去的,所以在开启Staticfile的情况下,直接把XXXX.txt文件放置到wwwroot目录中即可通过微信文件校验。说句题外话,微信这种校验方式,其实和Let's encrypt数字证书的校验是一样的,目的就是为了证明你确实是你声明的那个域名对应的服务器。

      Post方法,用来接收微信服务器推送过来的微信终端的消息,其中就用到了上述自定义消息处理器。

      CreateMenu用来提供创建微信菜单的api,我的做法是把微信菜单定义在menu.json中,然后代码读取并调用微信相关方法创建。之所以这样是因为菜单功能可能经常变化,所以做成配置化。生产环境中,记得给CreateMenu方法做鉴权,否则别人随便操你的菜单,那可不是好玩儿的。

      GetMenu,获取当前微信菜单,这个不必多说。

    4)微信相关服务&中间件注册

    Startup.ConfigService中添加如下片段:

     //微信相关服务
                services.AddSenparcGlobalServices(Configuration)
                        .AddSenparcWeixinServices(Configuration);  

      这是注册Senparc微信相关服务

    Startup.Config中添加如下片段注册Senparc相关中间件:

     IRegisterService register = RegisterService.Start(env, senparcSetting.Value)
                                                           .UseSenparcGlobal();
                register.RegisterTraceLog(() => ConfigTraceLog(monitorService));
                register.UseSenparcWeixin(senparcWeixinSetting.Value, senparcSetting.Value)  
                    .RegisterMpAccount(senparcWeixinSetting.Value, "Fuck XXXXXX");
  • 相关阅读:
    androidstudio提示adb错误:cannot parse version string:kg01的解决方法
    mysql常用运行原理
    shiro认证授权
    Java多线程入门(一)——多线程基本概念
    CSS简单入门
    H5入门这一篇就够了
    Java IO流详解(八)——其他流的使用
    Java IO流详解(七)——对象流(序列化与反序列化)
    Java IO流详解(六)——转换流
    Java IO流详解(五)——缓冲流
  • 原文地址:https://www.cnblogs.com/guokun/p/11891801.html
Copyright © 2020-2023  润新知