欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-01 17:50:37
1、前言 既然是.net下微信开发,自然少不了Senparc,可以说这个框架的存在, 至少节省了微信相关工作量的80%。事实上,项目开始前,还纠结了下是Java还是core,之所以最终选择core,除了情怀外,更重要的便是这个微信开发框架的存在。本项目的整合方式,极大程度上参考了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");