微信第三方平臺開發-從開始到測試

一:說明

*當前文檔比較簡陋,如果有其他方法不明確可以留言。

項目框架:.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;
                }
            }

        }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章