黄学彪
2020-12-16 0d9f64668fd7350d6a21fd157e32009a96d98134
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
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
    }
}