微信第三方平台开发-从开始到测试

一:说明

*当前文档比较简陋,如果有其他方法不明确可以留言。

项目框架:.net freamwork 4.6.1

开发工具:vs2019

本次开发使用Senparc.Weixin组件,地址:https://github.com/JeffreySu/WeiXinMPSDK

相关版本如下:

 

二:名次解释

Token:下图token

EncodingAESKey:下图key

AppId:下图appid

 

三:刚需接口

1:授权推送及component_verify_ticket接收

[HttpPost]
        public HttpResponseMessage Auth([FromUri]Senparc.Weixin.Open.Entities.Request.PostModel postModel)
        {
            var wcs = new WeChatService();
            try
            {
                if (postModel != null)
                {
                    postModel.Token = "你的token"
                    postModel.AppId = "你的appid"
                    postModel.EncodingAESKey = "你的key"
                }
                var inputStream = Request.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
                XDocument encryptDoc = XmlUtility.Convert(inputStream);
                //获取解密后的XDocument
                XDocument decryptDoc = WeChatHelper.GetDecryptXDocument(encryptDoc, postModel.AppId, postModel.Token, postModel.EncodingAESKey, postModel.Msg_Signature, postModel.Timestamp, postModel.Nonce);
                Senparc.Weixin.Open.IRequestMessageBase requestMessage = Senparc.Weixin.Open.RequestMessageFactory.GetRequestEntity(decryptDoc);
                if (requestMessage == null)
                {
                    LogHelper.Warn("接收component_verify_ticket方法,requestMessage:接收信息为空");
                }
                else
                {
                    LogHelper.Info("接收到通知:" + requestMessage.JSONSerialize());
                    switch (requestMessage.InfoType)
                    {
                        case RequestInfoType.component_verify_ticket://推送component_verify_ticket

                            var messageComponentVerifyTicket = requestMessage as RequestMessageComponentVerifyTicket;
                            string verifyTicket = messageComponentVerifyTicket.ComponentVerifyTicket;
                            new WeChatService().SaveTicket(verifyTicket, postModel.AppId); // 存储推送的verifyTicket
                            break;
                        //我的授权工作是在url回调时完成的,此处并未真正执行。
                        case RequestInfoType.authorized://授权成功
                            var requestMessageAuthorized = requestMessage as RequestMessageAuthorized;
                            string authorizerAppid = requestMessageAuthorized.AuthorizerAppid;
                            LogHelper.Info("接收component_verify_ticket" + "方法ReceiveRequestType,信息6:授权成功!requestMessageAuthorized:" + JsonConvert.SerializeObject(requestMessageAuthorized));
                            if (!String.IsNullOrEmpty(authorizerAppid))
                            {
                                //wcs.UpdateOfficialStatus(authorizerAppid, 1);
                            }
                            break;

                        case RequestInfoType.unauthorized://公众号取消授权

                            var requestMessageUnauthorized = requestMessage as RequestMessageUnauthorized;
                            string authorizerAppid01 = requestMessageUnauthorized.AuthorizerAppid;
                            LogHelper.Info("接收component_verify_ticket" + "方法ReceiveRequestType,信息7:取消授权!requestMessageUnauthorized:" + JsonConvert.SerializeObject(requestMessageUnauthorized));

                            if (!String.IsNullOrEmpty(authorizerAppid01))
                            {
                                wcs.UpdateOfficialStatus(authorizerAppid01, 3);
                            }
                            break;

                        case RequestInfoType.updateauthorized://公众号更新授权

                            var requestMessageUpdateAuthorized = requestMessage as RequestMessageUpdateAuthorized;
                            string authorizerAppid02 = requestMessageUpdateAuthorized.AuthorizerAppid;
                            LogHelper.Info("接收component_verify_ticket" + "方法ReceiveRequestType,信息8:更新授权!requestMessageUpdateAuthorized:" + JsonConvert.SerializeObject(requestMessageUpdateAuthorized));

                            if (!String.IsNullOrEmpty(authorizerAppid02))
                            {
                                wcs.UpdateOfficialStatus(authorizerAppid02, 2);
                            }
                            break;

                        default:
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Info("接收component_verify_ticket"+"方法Auth:异常信息:" + ex.ToString());
            }

            var resutl = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
            resutl.Content = new StringContent("success");
            return resutl;
        }

2: 获取ComponentToken

/// <summary>
        /// 获取ComponentToken
        /// </summary>
        /// <param name="isRefresh"></param>
        /// <returns></returns>
        public static string GetComponentToken(bool isRefresh =false)
        {
            //1:先判断数据库或者缓存里面的token是否有效,如果是强制刷新则不从缓存或数据库取。
            //代码忽略
            
            //2:如果token为空或者已过期或者强制刷新token
            //先取ticket
            var ticket = "从缓存或数据库获取";
            if (ticket.IsNullOrEmptyAndTrim())
            {
                LogHelper.Error($"获取Component_Token时,Redis读取ticket失败,key:{ticketKey}");
                return "";
            }
            var dict = new Dictionary<string, object>();
            dict.Add("component_appid", WeChatUtils.Component_AppId);
            dict.Add("component_appsecret", WeChatUtils.Component_AppSecret);
            dict.Add("component_verify_ticket", ticket);
            var token =HttpClientHelper.DoPostAsync<WCComponentToken>(WeChatUtils.URL_GetComponentToken,dict).GetAwaiter().GetResult();
            if (token != null && !token.component_access_token.IsNullOrEmptyAndTrim())
            {
               //获取成功。此处应该把token存起来。
            }
            return token.component_access_token;
        }

3:处理微信推送的公众号消息或者事件。 目前只处理了文本消息和关注、取消关注事件。

        [HttpPost]
        [HttpOptions]
        //微信后台设置的事件接收路径是:http://www.xxx.com/wechat/callback/$APPID$
        public HttpResponseMessage CallBack([FromUri]Senparc.Weixin.MP.Entities.Request.PostModel postModel)
        {
            //其实当前方法并未接收$APPID$的参数。因为解析事件的时候,可以获取到是哪个公众号推送的事件。
            LogHelper.Info($"进入事件处理:"+Request.RequestUri.ToString());

            if (postModel != null)
                {
                    postModel.Token = WeChatUtils.Component_Token;
                    postModel.AppId = WeChatUtils.Component_AppId;
                    postModel.EncodingAESKey = WeChatUtils.Component_Key;
                }
            try
            {
                MessageHandler<CustomMessageContext> messageHandler = new CustomMessageHandler(Request.Content.ReadAsStreamAsync().GetAwaiter().GetResult(), postModel);

                messageHandler.Execute(); //执行
                var result = new FixWeixinBugWeixinResult(messageHandler);
                LogHelper.Info($"返回结果:"+result.Content);
                if (!result.Content.IsNullOrEmptyAndTrim()) 
                {
                    //如果在CustomMessageHandler中返回了消息,则将消息推送到微信服务器。
                    //如果在CustomMessageHandler不想返回消息,一定要返回null。详见CustomMessageHandler
                    var str = result.Content;
                    return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
                    {
                        Content = new StringContent(str, System.Text.Encoding.UTF8, "application/xml")
                    };
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error("执行出现错误"+ex.ToString());   
            }

            var res = new HttpResponseMessage(HttpStatusCode.OK);
            res.Content = new StringContent("success");
            return res;
        }

3.1 CustomMessageHandler

public class CustomMessageHandler : MessageHandler<CustomMessageContext>
    {
        public CustomMessageHandler(Stream inputStream, PostModel postModel)
            : base(inputStream, postModel)
        {
        }

        /// <summary>
        /// 如果不需要返回值,则返回null。否则返回值会使用xml包裹。
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
        {
            LogHelper.Info($"进入了其他事件处理程序:" + requestMessage.JSONSerialize());
            //当前页面只处理了公众号文本消息,其他消息如:客户向公众号发了一张图片、发了语音、定位等等;
            //其他事件如关注事件、取消关注事件等等都没有单独处理,所以没有单独override的消息或事件,会进入当前方法。
            //返回一个默认的文本消息
            //var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
            //responseMessage.Content = "你好,你正在操作什么事情";
            //return responseMessage;
            return null; //如果用户的操作你不想返回任何消息,此处请返回null。
        }

        //重写文本接收事件
        public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
        {
            //测试逻辑
            var response = base.CreateResponseMessage<ResponseMessageText>();
            //打印接收的文本
            response.Content = "你好,你输入的文本是"+requestMessage.Content;
            return response;
            //requestMessage.FromUserName(用户的openID)
            //requestMessage.ToUserName(公众号的原始 ID;第三方平台公众号授权后获取公众号信息中的username。此处可以通过username反查公众号的appId,所以在接受消息推送时,没有存储$APPID$)
        }

        /// <summary>
        /// 关注公众号事件
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase OnEvent_SubscribeRequest(RequestMessageEvent_Subscribe requestMessage)
        {
            //关注公众号,我就把关注用的信息进行了存储。
            //用户信息存储有2种方法,第一种是我当前这种。通过requestMessage.ToUserName获取到的公众号的token和用户的requestMessage.FromUserName(用户的openID),去获取。弊端是用户没有关注过当前公众号,是获取不到的。订阅号没有权限也不可以。
            //地址 https://api.weixin.qq.com/cgi-bin/user/info?access_token={token}&openid={openID}&lang=zh_CN
            //还有一种方式就是在公众号里进行授权,然后通过静默授权或者用户点同意授权,获取到用户的信息。订阅号没有权限也不可以。

            new WeChatCommand().SaveWeChatUser(requestMessage.FromUserName, requestMessage.ToUserName);
            return null; //执行完了自己的方法,如果不需要返回给用户数据,记得返回null。
        }
        /// <summary>
        /// 取消关注公众号
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase OnEvent_UnsubscribeRequest(RequestMessageEvent_Unsubscribe requestMessage)
        {
            return base.OnEvent_UnsubscribeRequest(requestMessage);
        }
    }

3.2 CustomMessageContext

public class CustomMessageContext : DefaultMpMessageContext
    {
        public CustomMessageContext()
        {
            base.MessageContextRemoved += CustomMessageContext_MessageContextRemoved;
        }


        /// <summary>
        /// 当上下文过期,被移除时触发的时间
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CustomMessageContext_MessageContextRemoved(object sender, Senparc.NeuChar.Context.WeixinContextRemovedEventArgs<IRequestMessageBase, IResponseMessageBase> e)
        {
            /* 注意,这个事件不是实时触发的(当然你也可以专门写一个线程监控)
                * 为了提高效率,根据WeixinContext中的算法,这里的过期消息会在过期后下一条请求执行之前被清除
                */

            var messageContext = e.MessageContext as CustomMessageContext;
            if (messageContext == null)
            {
                return;//如果是正常的调用,messageContext不会为null
            }

            //TODO:这里根据需要执行消息过期时候的逻辑,下面的代码仅供参考

            //Log.InfoFormat("{0}的消息上下文已过期",e.OpenId);
            //api.SendMessage(e.OpenId, "由于长时间未搭理客服,您的客服状态已退出!");
        }
    }

四:全网测试发布。

全网测试分3块:

1:测试接口component_verify_ticket是否可以正常接收和返回,此测试不用特别处理。我们上面返回的就是“success”;

2:向用户发送文本消息:TESTCOMPONENT_MSG_TYPE_TEXT,然后被动回复TESTCOMPONENT_MSG_TYPE_TEXT_callback。此处在消息处理的地方判断一下就可以。

3:向用户发送文本消息:QUERY_AUTH_CODE:$query_auth_code$。($query_auth_code$是授权后的授权码,可以通过$query_auth_code$获取授权公众号的AccessToken,继而通过AccessToken获取到公众号详情和一些以其他公众号操作)

后台接收到文本消息后还是先判断,消息是否包含"QUERY_AUTH_CODE",如果是,就判断是在进行测试。$query_auth_code$获取到公众号的AccessToken,然后使用AccessToken发送返回消息:$query_auth_code$_from_api。

2、3代码如下:

public class CustomMessageHandler : MessageHandler<CustomMessageContext>
    {
        public CustomMessageHandler(Stream inputStream, PostModel postModel)
            : base(inputStream, postModel)
        {
        }

        /// <summary>
        /// 如果不需要返回值,则返回null。否则返回值会使用xml包裹。
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
        {
            LogHelper.Info($"进入了其他事件处理程序:" + requestMessage.JSONSerialize());
            //当前页面只处理了公众号文本消息,其他消息如:客户向公众号发了一张图片、发了语音、定位等等;
            //其他事件如关注事件、取消关注事件等等都没有单独处理,所以没有单独override的消息或事件,会进入当前方法。
            //返回一个默认的文本消息
            //var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
            //responseMessage.Content = "你好,你正在操作什么事情";
            //return responseMessage;
            return null; //如果用户的操作你不想返回任何消息,此处请返回null。
        }

        public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
        {
            var response = base.CreateResponseMessage<ResponseMessageText>();
            response.Content = "";

            LogHelper.Info("进入了文字处理程序"+requestMessage.JSONSerialize());

            #region 监测全网发布
            if (requestMessage.Content == "TESTCOMPONENT_MSG_TYPE_TEXT")
            {
                LogHelper.Info($"进入了全网测试第一阶段:测试公众号处理用户消息");
                response.Content = "TESTCOMPONENT_MSG_TYPE_TEXT_callback";
                return response;
            }
            else if (requestMessage.Content.Contains("QUERY_AUTH_CODE:"))
            {
                LogHelper.Info($"进入了全网测试第二阶段:测试公众号处理用户消息");
                var queryAuthCode = requestMessage.Content.Replace("QUERY_AUTH_CODE:", "");
                LogHelper.Info($"进入了全网测试第二阶段:queryAuthCode:"+ queryAuthCode);
                //通过微信传过来的公众号的QUERY_AUTH_CODE,直接拿公众号的AccessToken,拿到了公众号的AccessToken,就可以通过客服来发消息。
                var token = WeChatHelper.GetOfficial_Token(queryAuthCode);
                if (token == null || !token.errcode.IsNullOrEmptyAndTrim())
                {
                    LogHelper.Info($"通过Code获取Token方式失败:{token.JSONSerialize()}");
                    return null;
                }
                var msg = $"{queryAuthCode}_from_api";
                CustomApi.SendText(token.authorization_info.authorizer_access_token, requestMessage.FromUserName, msg);
                return null;
            }
            return null;
            #endregion
            
        }       
    }
public static WCOfficialToken GetOfficial_Token(string authorization_code)
        {
            if (authorization_code.IsNullOrEmptyAndTrim())
                return null;
            
            var token = GetComponentToken();
            if (token.IsNullOrEmptyAndTrim())
            {
                LogHelper.Error($"在获取Official_Token时,获取ComponentToken失败");
                return null;
            }
            var url = $"https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token={token}";
            var dict = new Dictionary<string, object>();
            dict.Add("component_access_token", token);
            dict.Add("component_appid", WeChatUtils.Component_AppId);
            dict.Add("authorization_code", authorization_code);
            var result =HttpClientHelper.DoPostAsync<WCOfficialToken>(url, dict).GetAwaiter().GetResult();
            if (result != null && result.errcode.IsNullOrEmptyAndTrim())
            {
                return result;
            }
            else
            {
                LogHelper.Error($"获取OfficialInfo失败!url:{url};dict:{dict};result:{result.JSONSerialize()}");
                return null;
            }
        }
/// <summary>
        /// Post请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="dictionary"></param>
        /// <returns></returns>
        public static async Task<T> DoPostAsync<T>(string url, Dictionary<string, object> dictionary)
        {
            //设置HttpClientHandler的AutomaticDecompression 自动压缩方式
            var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip };

            //创建HttpClient
            using (var http = new System.Net.Http.HttpClient(handler))
            {
                HttpContent httpContent = new StringContent(dictionary.JSONSerialize());
                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                try
                {
                    var result = await http.PostAsync(url, httpContent).Result.Content.ReadAsStringAsync();
                    var obj = new 
                    {
                        Url =url,
                        Result =result
                    };
                    LogHelper.Info("Post请求:"+obj.JSONSerialize());
                    return JsonConvert.DeserializeObject<T>(result, new TimestampConverter());
                }
                catch (Exception ex)
                {
                    LogHelper.Error("接口地址:" + url + ", 接口参数:" + dictionary.JSONSerialize());
                    throw ex;
                }
            }

        }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值