黄学彪
2020-12-17 9f326f4000847e6167d8166fa2f6a66f53cb3734
ZigbeeApp/Shared/Phone/Common/Logic/HdlHttpLogic.cs
New file
@@ -0,0 +1,565 @@
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace Shared.Phone
{
    /// <summary>
    /// Http访问逻辑
    /// </summary>
    public class HdlHttpLogic
    {
        //2020.08.04 为什么访问云端弄的那么复杂?我也不知道云端的意义为何
        //总之,云端就是弄得这么复杂,没办法
        #region ■ 变量声明___________________________
        /// <summary>
        /// http请求逻辑
        /// </summary>
        private static HdlHttpLogic m_Current = null;
        /// <summary>
        /// http请求逻辑
        /// </summary>
        public static HdlHttpLogic Current
        {
            get
            {
                if (m_Current == null)
                {
                    m_Current = new HdlHttpLogic();
                }
                return m_Current;
            }
        }
        /// <summary>
        /// 请求地址
        /// </summary>
        public string RequestHttpsHost = "https://test-gz.hdlcontrol.com";
        /// <summary>
        /// 默认的访问地址(上面的那个会变的)
        /// </summary>
        private const string DefultHttpsHost = "https://test-gz.hdlcontrol.com";
        #endregion
        #region ■ Http请求实行_______________________
        /// <summary>
        /// 请求服务器方法
        /// </summary>
        /// <param name="i_RequestName">访问地址</param>
        /// <param name="i_method">POST 或者 GET 等等</param>
        /// <param name="i_bodyObj">承载在body里面的类对象</param>
        /// <param name="i_dicQueryTip">存放文档上标签为【query】的变量,无此标签,则请设置为null</param>
        /// <param name="i_dicPathTip">存放文档上标签为【path】的变量,无此标签,则请设置为null</param>
        /// <param name="i_checkMode">当接口需要区分【主账号】和【管理员】时,请选择【A账号权限】</param>
        /// <param name="i_setAgain">当发送失败时,是否重发,默认不重发</param>
        /// <param name="i_timeout">超时(秒),默认3秒</param>
        /// <param name="i_isBasicService">是否是基础服务的接口(基础服务的接口需要 appKey,timestamp,sign这三个参数,当为true时,内部会自动添加)</param>
        /// <returns>得到响应的数据</returns>
        public ResponsePack RequestResponseFromZigbeeHttps(string i_RequestName, Method i_method,
            object i_bodyObj, Dictionary<string, object> i_dicQueryTip = null, Dictionary<string, object> i_dicPathTip = null,
            CheckMode i_checkMode = CheckMode.A不检测, bool i_setAgain = false, int i_timeout = 3, bool i_isBasicService = false)
        {
            //请求Url的完成路径
            var result = this.RequestByteFromZigbeeHttps(i_RequestName, i_method, i_bodyObj, i_dicQueryTip, i_dicPathTip, i_checkMode, i_setAgain, i_timeout, i_isBasicService);
            //转换数据为 ResponsePack
            return this.ConvertData(result);
        }
        /// <summary>
        /// 访问指定接口,并直接返回接口返回的比特,存在错误信息时,返回null
        /// </summary>
        /// <returns>返回:并直接返回接口返回的比特,存在错误信息时,返回null</returns>
        /// <param name="i_RequestName">访问地址</param>
        /// <param name="i_method">POST 或者 GET 等等</param>
        /// <param name="i_bodyObj">承载在body里面的类对象</param>
        /// <param name="i_dicQueryTip">存放文档上标签为【query】的变量,无此标签,则请设置为null</param>
        /// <param name="i_dicPathTip">存放文档上标签为【path】的变量,无此标签,则请设置为null</param>
        /// <param name="i_checkMode">当接口需要区分【主账号】和【管理员】时,请选择【A账号权限】</param>
        /// <param name="i_setAgain">当发送失败时,是否重发,默认不重发</param>
        /// <param name="i_timeout">超时(秒),默认3秒</param>
        /// <param name="i_isBasicService">是否是基础服务的接口(基础服务的接口需要 appKey,timestamp,sign这三个参数,当为true时,内部会自动添加)</param>
        public byte[] RequestByteFromZigbeeHttps(string i_RequestName, Method i_method,
            object i_bodyObj, Dictionary<string, object> i_dicQueryTip = null, Dictionary<string, object> i_dicPathTip = null,
            CheckMode i_checkMode = CheckMode.A不检测, bool i_setAgain = false, int i_timeout = 3, bool i_isBasicService = false)
        {
            //请求Url的完成路径
            var fullUrl = RequestHttpsHost + "/" + i_RequestName;
            //请求token
            var httpToken = this.GetHttpToken(i_checkMode);
            //获取从接口那里取到的比特数据
            var revertObj = this.DoRequestZigbeeHttps(fullUrl, httpToken, i_method, i_bodyObj, i_dicQueryTip, i_dicPathTip, i_checkMode, i_timeout, i_isBasicService);
            if (revertObj == null)
            {
                if (i_setAgain == false)
                {
                    //当前无法访问网络
                    return null;
                }
                revertObj = RequestByteFromZigbeeHttpsAgain(fullUrl, i_method, httpToken, i_bodyObj, i_dicQueryTip, i_dicPathTip, i_checkMode, i_timeout, i_isBasicService);
                if (revertObj == null)
                {
                    return null;
                }
            }
            return revertObj;
        }
        /// <summary>
        /// 私有类型:从新发送(懂的人自然懂,很难解释清楚)
        /// </summary>
        /// <param name="i_fullUrl">请求Url的完成路径</param>
        /// <param name="i_method">POST 或者 GET 等等</param>
        /// <param name="i_token">token</param>
        /// <param name="i_bodyObj">承载在body里面的类对象</param>
        /// <param name="i_dicQueryTip">云端文档上标签为【query】的变量,无此标签,则请设置为null</param>
        /// <param name="i_dicPathTip">云端文档上标签为【path】的变量,无此标签,则请设置为null</param>
        /// <param name="i_checkMode">当接口需要区分【主账号】和【管理员】时,请选择【A账号权限】</param>
        /// <param name="i_timeout">超时(秒)</param>
        /// <param name="i_isBasicService">是否是基础服务的接口(基础服务的接口需要 appKey,timestamp,sign这三个参数,当为true时,内部会自动添加)</param>
        /// <returns></returns>
        private byte[] RequestByteFromZigbeeHttpsAgain(string i_fullUrl, Method i_method, string i_token,
            object i_bodyObj, Dictionary<string, object> i_dicQueryTip, Dictionary<string, object> i_dicPathTip,
             CheckMode i_checkMode, int i_timeout, bool i_isBasicService)
        {
            byte[] responsePack = null;
            int count = 0;
            while (true)
            {
                System.Threading.Thread.Sleep(2000);
                if (HdlCheckLogic.Current.IsAccountLoginOut() == true)
                {
                    //如果已经退出了登录
                    return null;
                }
                //调用接口
                responsePack = this.DoRequestZigbeeHttps(i_fullUrl, i_token, i_method, i_bodyObj, i_dicQueryTip, i_dicPathTip, i_checkMode, i_timeout, i_isBasicService);
                if (responsePack != null)
                {
                    break;
                }
                count++;
                if (count == 1)
                {
                    //算了,目前就只重发一次好了
                    break;
                }
            }
            return responsePack;
        }
        /// <summary>
        /// 请求服务器方法,指定一个Url,和请求方法,数据,Cookie,得到响应的数据
        /// </summary>
        /// <param name="i_requestFullUrl">请求Url的完成路径</param>
        /// <param name="i_token">token(请勿传入长度为0的字符串,兼顾访问非河东的网址,这里如果为null,代表访问的是非河东的网址)</param>
        /// <param name="i_method">POST 或者 GET 等等</param>
        /// <param name="i_bodyObj">承载在body里面的类对象</param>
        /// <param name="i_dicQueryTip">存放文档上标签为【query】的变量,无此标签,则请设置为null</param>
        /// <param name="i_dicPathTip">存放文档上标签为【path】的变量,无此标签,则请设置为null</param>
        /// <param name="i_checkMode">当接口需要区分【主账号】和【管理员】时,请选择【A账号权限】</param>
        /// <param name="i_timeout">超时(秒)</param>
        /// <param name="i_isBasicService">是否是基础服务的接口(基础服务的接口需要 appKey,timestamp,sign这三个参数,当为true时,内部会自动添加)</param>
        /// <returns>得到响应的数据</returns>
        public byte[] DoRequestZigbeeHttps(string i_requestFullUrl, string i_token, Method i_method,
            object i_bodyObj, Dictionary<string, object> i_dicQueryTip, Dictionary<string, object> i_dicPathTip,
            CheckMode i_checkMode, int i_timeout, bool i_isBasicService)
        {
            //HttpWebRequest的超时时间,如果设置低于15秒,在没网的情况下,很大几率它的超时时间大于15秒
            //所以才这样整
            byte[] result = null;
            bool canBreak = false;
            new System.Threading.Thread(() =>
            {
                try
                {
                    RestClient client = new RestClient(i_requestFullUrl);
                    RestRequest request = new RestRequest(i_method);
                    request.Timeout = i_timeout * 1000;
                    request.AddHeader("content-type", "application/json");
                    //这个东西是token
                    if (i_token != null)
                    {
                        //Token前面要加上这个东西
                        request.AddHeader("authorization", Common.Config.Instance.HeaderPrefix + i_token);
                        //固定为2,代表是zigbeeApp
                        request.AddHeader("clientType", "2");
                    }
                    //加入到Body里面的数据
                    if (i_bodyObj != null)
                    {
                        if (i_bodyObj is byte[])
                        {
                            //这个东西应该是文件,不能反序列化,不然数据可能会丢失
                            request.AddParameter("application/json", i_bodyObj, ParameterType.RequestBody);
                        }
                        else if (i_isBasicService == true)
                        {
                            //访问基础服务接口(为了避免过多的装箱和拆箱,这样整一个else if出来)
                            string strBody = this.GetSignRequestJson(JsonConvert.SerializeObject(i_bodyObj));
                            request.AddParameter("application/json", strBody, ParameterType.RequestBody);
                        }
                        else
                        {
                            string strBody = JsonConvert.SerializeObject(i_bodyObj);
                            request.AddParameter("application/json", strBody, ParameterType.RequestBody);
                        }
                    }
                    else
                    {
                        if (i_isBasicService == true)
                        {
                            //访问基础服务接口(为了避免过多的装箱和拆箱,这样整一个else if出来)
                            string strBody = this.GetSignRequestJson("{}");
                            request.AddParameter("application/json", strBody, ParameterType.RequestBody);
                        }
                    }
                    //添加到参数里面的数据
                    if (i_dicQueryTip != null)
                    {
                        foreach (string dataKey in i_dicQueryTip.Keys)
                        {
                            if (i_dicQueryTip[dataKey] != null)
                            {
                                request.AddQueryParameter(dataKey.Trim(), i_dicQueryTip[dataKey].ToString());
                            }
                        }
                    }
                    //拼接到地址上的数据
                    if (i_dicPathTip != null)
                    {
                        foreach (var dataKey in i_dicPathTip.Keys)
                        {
                            if (i_dicPathTip[dataKey] != null)
                            {
                                request.AddUrlSegment(dataKey.Trim(), i_dicPathTip[dataKey].ToString());
                            }
                        }
                    }
                    IRestResponse response = client.Execute(request);
                    if (response != null && response.StatusCode == HttpStatusCode.OK)
                    {
                        result = response.RawBytes;
                    }
                    canBreak = true;
                }
                catch (WebException e)
                {
                    if (e.Status == WebExceptionStatus.ConnectFailure//由有网络切换到无网络时触发
                        || e.Status == WebExceptionStatus.Timeout//超时
                        || e.Status == WebExceptionStatus.NameResolutionFailure)//本身就是无网络时启动时触发
                    {
                    }
                    canBreak = true;
                }
                catch (Exception e)
                {
                    canBreak = true;
                }
            })
            { IsBackground = true }.Start();
            int timeCount = (i_timeout * 1000) / 20;
            while (canBreak == false && timeCount > 0)
            {
                System.Threading.Thread.Sleep(20);
                timeCount--;
            }
            //自动检测Token是否已经过期
            return this.CheckTokenIsTimeOut(i_requestFullUrl, i_method, i_bodyObj, i_dicQueryTip, i_dicPathTip, i_checkMode, i_timeout, i_isBasicService, result);
        }
        #endregion
        #region ■ 第三方接口访问_____________________
        /// <summary>
        /// 第三方接口访问
        /// </summary>
        /// <param name="requestFullUrl">请求Url的完成路径</param>
        /// <param name="contentType">text/html 或者 application/json</param>
        /// <param name="requestMethod">POST 或者 GET 等等</param>
        /// <param name="timeout">超时,默认5秒</param>
        /// <returns>得到响应的数据</returns>
        public byte[] RequestThridPartyHttps(string requestFullUrl, string contentType = "text/html", string requestMethod = "GET", int timeout = 5)
        {
            try
            {
                //初始化新的webRequst
                //1. 创建httpWebRequest对象
                var webRequest = (HttpWebRequest)WebRequest.Create(new Uri(requestFullUrl));
                //2. 初始化HttpWebRequest对象
                webRequest.Method = requestMethod;
                webRequest.Timeout = timeout * 1000;
                //取消使用代理访问
                webRequest.Proxy = null;
                webRequest.UseDefaultCredentials = false;
                webRequest.ContentType = contentType;
                //4. 读取服务器的返回信息
                var response = (HttpWebResponse)webRequest.GetResponse();
                using (var stream = response.GetResponseStream())
                {
                    if (stream == null)
                    {
                        return null;
                    }
                    var ms = new System.IO.MemoryStream();
                    var bytes = new byte[1024];
                    var len = int.MaxValue;
                    while (stream.CanRead && 0 < len)
                    {
                        len = stream.Read(bytes, 0, bytes.Length);
                        ms.Write(bytes, 0, len);
                    }
                    return ms.ToArray();
                }
            }
            catch
            {
                return null;
            }
        }
        #endregion
        #region ■ 云端访问密匙_______________________
        /// <summary>
        /// appKey(访问云端固定的东西)
        /// </summary>
        private const string APP_KEY = "HDL-HOME-APP-TEST";
        /// <summary>
        /// 我也不知道这是什么鬼东西
        /// </summary>
        private const string SECRET_KEY = "WeJ8TY88vbakCcnvH8G1tDUqzLWY8yss";
        /// <summary>
        /// 获取当前时间戳值(访问云端使用)
        /// </summary>
        /// <returns></returns>
        private string GetTimestamp()
        {
            System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); // 当地时区
            return ((long)(DateTime.Now - startTime).TotalSeconds).ToString(); // 相差秒数
        }
        /// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="signstr"></param>
        /// <returns></returns>
        private string SignMD5Encrypt(string s)
        {
            byte[] sign = System.Security.Cryptography.MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(s));
            string signstr = string.Empty;
            foreach (byte item in sign)
            {
                signstr += item.ToString("X2");
            }
            return signstr.ToLower();
        }
        /// <summary>
        /// 基础服务的接口都要校验sign,计算sign签名
        /// </summary>
        /// <param name="i_BodyData">body的数据</param>
        /// <returns></returns>
        private string GetSignRequestJson(string i_BodyData)
        {
            try
            {
                //1. 将model实体转为Dictionary<string, object>
                var paramDictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(i_BodyData);
                //2. 计算sign
                if (paramDictionary != null)
                {
                    paramDictionary.Add("appKey", APP_KEY);
                    paramDictionary.Add("timestamp", GetTimestamp());
                    //2.1 字典升序
                    paramDictionary = paramDictionary.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);
                    //2.2 拼接按URL键值对
                    string str = string.Empty;
                    foreach (KeyValuePair<string, object> item in paramDictionary)
                    {
                        if (item.Value == null)
                        {
                            //不校验
                            continue;
                        }
                        string myValue = item.Value.ToString();
                        if (myValue == string.Empty)
                        {
                            //空字符也不校验
                            continue;
                        }
                        if (item.Value is bool)
                        {
                            //云端那帮沙雕说bool类型必须用小写
                            myValue = myValue.ToLower();
                        }
                        str += item.Key + "=" + myValue + "&";
                    }
                    //2.3 拼接SECRET_KEY
                    str = str.Substring(0, str.Length - 1) + SECRET_KEY;
                    //2.4 MD5转换+转小写
                    var signstr = SignMD5Encrypt(str);
                    paramDictionary.Add("sign", signstr);
                    return JsonConvert.SerializeObject(paramDictionary);
                }
                else
                {
                    return "";
                }
            }
            catch
            {
                return "";
            }
        }
        #endregion
        #region ■ 自动检测Token______________________
        /// <summary>
        /// 自动检测Token是否已经过期
        /// </summary>
        /// <param name="i_requestFullUrl">请求Url的完成路径</param>
        /// <param name="i_method">POST 或者 GET 等等</param>
        /// <param name="i_bodyObj">承载在body里面的类对象</param>
        /// <param name="i_dicQueryTip">存放文档上标签为【query】的变量,无此标签,则请设置为null</param>
        /// <param name="i_dicPathTip">存放文档上标签为【path】的变量,无此标签,则请设置为null</param>
        /// <param name="i_checkMode">当接口需要区分【主账号】和【管理员】时,请选择【A账号权限】</param>
        /// <param name="i_timeout">超时(秒)</param>
        /// <param name="i_isBasicService">是否是基础服务的接口(基础服务的接口需要 appKey,timestamp,sign这三个参数,当为true时,内部会自动添加)</param>
        /// <param name="i_result">接口返回的数据</param>
        private byte[] CheckTokenIsTimeOut(string i_requestFullUrl, Method i_method,
            object i_bodyObj, Dictionary<string, object> i_dicQueryTip, Dictionary<string, object> i_dicPathTip,
            CheckMode i_checkMode, int i_timeout, bool i_isBasicService, byte[] i_result)
        {
            if (i_result == null || i_result.Length == 0 || i_result[0] != '{' || i_result[i_result.Length - 1] != '}')
            {
                //以上都不是正规的json
                return i_result;
            }
            var result = this.ConvertData(i_result);
            if (result == null)
            {
                return i_result;
            }
            if (result.Code == HttpMessageEnum.A繁忙 || result.Code == HttpMessageEnum.A超时)
            {
                HdlMessageLogic.Current.ShowNetCodeTipMsg(ShowNetCodeMode.YES, result);
                return null;
            }
            if (result.Code == HttpMessageEnum.A40001  //这个是token过期
                || result.Code == HttpMessageEnum.A10006) //这个是token不对劲
            {
                bool seccess = false;
                if (i_checkMode == CheckMode.A账号权限)
                {
                    //重新刷新主人的token
                    seccess = HdlAccountLogic.Current.InitMasterToken();
                }
                else
                {
                    //这个是token过期
                    var reponse = HdlAccountLogic.Current.LoginByRefreshToken();
                    if (reponse == null || reponse.Code != HttpMessageEnum.A成功)
                    {
                        seccess = false;
                    }
                }
                //如果刷新token失败,则重新登陆
                if (seccess == false)
                {
                    HdlThreadLogic.Current.RunMain(() =>
                    {
                        //登陆Token已经过期,请重新登陆
                        string msg = Language.StringByID(R.MyInternationalizationString.uLoginTokenIsTimeOutPleaseLoginAgain);
                        HdlMessageLogic.Current.ShowMassage(ShowMsgType.Tip, msg);
                        HdlAccountLogic.Current.ReLoginAgain(HdlUserCenterResourse.UserInfo.Account);
                    });
                    return null;
                }
                //重新调用接口
                var httpToken = this.GetHttpToken(i_checkMode);
                return this.DoRequestZigbeeHttps(i_requestFullUrl, Common.Config.Instance.Token, i_method, i_bodyObj, i_dicQueryTip, i_dicPathTip, i_checkMode, i_timeout, i_isBasicService);
            }
            return i_result;
        }
        #endregion
        #region ■ 一般方法___________________________
        /// <summary>
        /// 获取接口访问的token
        /// </summary>
        /// <param name="i_checkMode">是否检测权限</param>
        /// <returns></returns>
        public string GetHttpToken(CheckMode i_checkMode)
        {
            if (i_checkMode == CheckMode.A不检测)
            {
                return Common.Config.Instance.Token;
            }
            if (HdlUserCenterResourse.ResidenceOption.AuthorityNo == 2)
            {
                //管理员则使用主人的token
                return Common.Config.Instance.MasterToken;
            }
            return Common.Config.Instance.Token;
        }
        /// <summary>
        /// 重置云端默认的访问地址
        /// </summary>
        public void ResetDefultHttpsHost()
        {
            this.RequestHttpsHost = DefultHttpsHost;
        }
        /// <summary>
        /// 转换数据
        /// </summary>
        /// <param name="i_result">云端访问的byte数据(不是json数据不要调用)</param>
        /// <returns></returns>
        public ResponsePack ConvertData(byte[] i_result)
        {
            if (i_result == null)
            {
                return null;
            }
            try
            {
                var resultData = Encoding.UTF8.GetString(i_result);
                var data = JsonConvert.DeserializeObject<ResponsePack>(resultData);
                return data;
            }
            catch { }
            return null;
        }
        #endregion
    }
}