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

一、前言

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

二、整合方式

1)增长Senparc配置节

appsettings.json中添加以下配置节: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,减小调试信息及提升性能。api

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方法作鉴权,不然别人随便操你的菜单,那可不是好玩儿的。app

  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");
相关文章
相关标签/搜索