终端一体化运控平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

439 line
16 KiB

  1. using BPA.Helper;
  2. using BPA.Message.IOT;
  3. using BPASmartDatavDeviceClient.IoT;
  4. using DataVAPI.Tool.IOT;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using static AlibabaCloud.SDK.Iot20180120.Models.QueryDeviceFileResponseBody;
  13. namespace BPASmartClient.IoT.Model
  14. {
  15. /// <summary>
  16. /// 阿里云文件上传
  17. /// </summary>
  18. public class FileUpload
  19. {
  20. public static string FileName = $"HBL.LogDir{DateTime.Now.ToString("yyyy_M_d")}";
  21. public static string path = $"{System.AppDomain.CurrentDomain.BaseDirectory}LogDir\\{FileName}.log";
  22. /// <summary>
  23. /// 一次性上传的包
  24. /// </summary>
  25. public static byte[] FileBlock;
  26. /// <summary>
  27. /// 请求反馈信息
  28. /// </summary>
  29. public static FileUploadModelResult modelResult = null;
  30. /// <summary>
  31. /// 文件请求上传
  32. /// </summary>
  33. public static void FileRequest(DataVReport dataV)
  34. {
  35. TaskManage.GetInstance.Start(new Action(() =>
  36. {
  37. try
  38. {
  39. List<QueryDeviceFileResponseBodyData> queryDeviceFiles = Sample.FileQueryALL(DataVClient.GetInstance.DeviceName);
  40. QueryDeviceFileResponseBodyData _data = queryDeviceFiles?.Find(par => par.Name == FileName);
  41. if (_data != null)//删除文件
  42. {
  43. MessageLog.GetInstance.Show("阿里云设备下文件已存在,准备删除续传...");
  44. Sample.DeleteFile(DataVClient.GetInstance.DeviceName, _data.FileId);
  45. MessageLog.GetInstance.Show("删除成功.准备上传..");
  46. }
  47. byte[] FileBlockALL = FileContent(path);
  48. //分包上传
  49. int length = FileBlockALL.Count();
  50. int bfb = (FileBlockALL.Count() / 130000) + 1;
  51. MessageLog.GetInstance.Show($"[阿里云上传]:文件整理中总长度[{length}]字节,分包数[{bfb}]包.");
  52. //分包上传
  53. for (int i = 0; i < bfb; i++)
  54. {
  55. modelResult = null;
  56. byte[] data;
  57. if (i == bfb - 1) data = new byte[FileBlockALL.Count() - i * 130000];//最后一包
  58. else data = new byte[130000];//前面的包
  59. for (int k = 0; k < data.Length; k++)
  60. {
  61. data[k] = FileBlockALL[k + i * 130000];
  62. }
  63. FileBlock = data;
  64. FileUploadModel fileUpload = new FileUploadModel();
  65. fileUpload.@params.fileName = FileName;
  66. fileUpload.@params.fileSize = -1;
  67. fileUpload.@params.conflictStrategy = length > 130000 ? "append" : "overwrite";
  68. //fileUpload.@params.conflictStrategy = "append";// "overwrite";//覆盖模式
  69. //fileUpload.@params.ficMode = "crc64";
  70. //fileUpload.@params.ficValue = CRC.ToCRC16(FileBlock);
  71. fileUpload.@params.initUid = $"ab{RandomHelper.GenerateRandomCode()}";
  72. //上传到阿里云物联网平台的OSS存储空间中
  73. fileUpload.@params.extraParams.ossOwnerType = "iot-platform";
  74. //表示上传到设备所属用户自己的OSS存储空间中
  75. //{ossbucket}/aliyun-iot-device-file/${instanceId}/${productKey}/${serviceId}/${deviceName}/${fileName}
  76. //fileUpload.@params.extraParams.ossOwnerType = "device-user";
  77. //fileUpload.@params.extraParams.serviceId = "black";
  78. //fileUpload.@params.extraParams.fileTag = new Dictionary<string, string> { {"Time", DateTime.Now.ToString("yyyy_M_d") },{"Name", "HBL.LogDir" } };
  79. dataV.IOT_Publish(dataV.FileUpLoadTopic, Tools.JsonConvertTools(fileUpload));
  80. //MessageLog.GetInstance.Show($"[阿里云上传]:第[{i+1}]包数据,请求上传.");
  81. int count = 0;
  82. while (modelResult == null)//等待上传完成
  83. {
  84. if (count > 5)
  85. {
  86. MessageLog.GetInstance.Show($"[阿里云上传]:超时上传、未知原因、退出线程.");
  87. return;
  88. }
  89. Thread.Sleep(3000); count++;
  90. }
  91. ;
  92. if (modelResult.code == 200)
  93. {
  94. if (modelResult.data.complete)
  95. {
  96. MessageLog.GetInstance.Show($"[阿里云上传]:文件上传完成,总长度[{modelResult.data.bSize}]字节.");
  97. }
  98. }
  99. else
  100. MessageLog.GetInstance.Show($"[阿里云上传]:第[{i + 1}]包,上传失败.原因:{modelResult.message}");
  101. ;
  102. }
  103. }
  104. catch (Exception ex)
  105. {
  106. MessageLog.GetInstance.ShowEx(ex.ToString());
  107. }
  108. }), "文件请求上传", isRestart: false);
  109. }
  110. /// <summary>
  111. /// 文件上传
  112. /// </summary>
  113. public static void FileSend(DataVReport dataV, string uploadId, long offset)
  114. {
  115. FileSendModel fileSend = new FileSendModel();
  116. fileSend.@params.uploadId = uploadId;
  117. fileSend.@params.offset = offset;
  118. fileSend.@params.bSize = FileBlock.Count();
  119. fileSend.@params.isComplete = FileBlock.Count() == 130000 ? false : true;
  120. //结构如下图
  121. //Header.length(高 低) + Header(字节数组UTF-8) + 文件分片的字节数组 + "分片校验值CrC16 低 高"
  122. byte[] Header = Encoding.UTF8.GetBytes(Tools.JsonConvertTools(fileSend));
  123. byte HeaderLen_L = (byte)(Header.Length);
  124. byte HeaderLen_H = (byte)(Header.Length >> 8);
  125. byte[] CRC16 = CRC16Standard.getCRCBytes(FileBlock);//CRC.CRC16(FileBlock);
  126. List<byte> message = new List<byte>() { HeaderLen_H, HeaderLen_L };
  127. message.AddRange(Header);
  128. message.AddRange(FileBlock);
  129. message.AddRange(CRC16);
  130. dataV.IOT_Publish(dataV.FileUpLoadSendTopic, message.ToArray());
  131. }
  132. /// <summary>
  133. /// 取消文件上传
  134. /// </summary>
  135. public static void FileCancelSend(DataVReport dataV, string uploadId)
  136. {
  137. CancelSend cancel = new CancelSend(uploadId);
  138. dataV.IOT_Publish(dataV.CancelFileUpLoadSendTopic, Tools.JsonConvertTools(cancel));
  139. }
  140. /// <summary>
  141. /// 读取文件
  142. /// </summary>
  143. /// <param name="fileName"></param>
  144. /// <returns></returns>
  145. private static byte[] FileContent(string fileName)
  146. {
  147. using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  148. {
  149. try
  150. {
  151. byte[] buffur = new byte[fs.Length];
  152. fs.Read(buffur, 0, (int)fs.Length);
  153. return buffur;
  154. }
  155. catch (Exception ex)
  156. {
  157. return new byte[0];
  158. }
  159. }
  160. }
  161. /// <summary>
  162. /// 读取文件字节
  163. /// </summary>
  164. /// <param name="path"></param>
  165. /// <returns></returns>
  166. public static byte[] ReadFile(string path)
  167. {
  168. //创建d:\file.txt的FileStream对象
  169. FileStream fstream = new FileStream($@"{path}", FileMode.OpenOrCreate);
  170. byte[] bData = new byte[fstream.Length];
  171. //设置流当前位置为文件开始位置
  172. fstream.Seek(0, SeekOrigin.Begin);
  173. //将文件的内容存到字节数组中(缓存)
  174. fstream.Read(bData, 0, bData.Length);
  175. string result = Encoding.UTF8.GetString(bData);
  176. Console.WriteLine(result);
  177. if (fstream != null)
  178. {
  179. //清除此流的缓冲区,使得所有缓冲的数据都写入到文件中
  180. fstream.Flush();
  181. fstream.Close();
  182. }
  183. return bData;
  184. }
  185. }
  186. #region 请求上传文件
  187. /// <summary>
  188. /// 请求上传文件
  189. /// </summary>
  190. public class FileUploadModel
  191. {
  192. /// <summary>
  193. /// 消息ID号
  194. /// 0~4294967295
  195. /// </summary>
  196. public string id { get; set; }
  197. /// <summary>
  198. /// 请求业务参数
  199. /// </summary>
  200. public BusinessParameters @params { get; set; }
  201. public FileUploadModel()
  202. {
  203. id = RandomHelper.GenerateRandomCode();
  204. @params = new BusinessParameters();
  205. }
  206. }
  207. /// <summary>
  208. /// 业务参数
  209. /// </summary>
  210. public class BusinessParameters
  211. {
  212. /// <summary>
  213. /// 设备上传文件的名称
  214. /// </summary>
  215. public string fileName { get; set; }
  216. /// <summary>
  217. /// 上传文件大小,单位字节。单个文件大小不超过16 MB。
  218. /// 取值为-1时,表示文件大小未知。文件上传完成时,需在上传文件分片的消息中,指定参数isComplete
  219. /// </summary>
  220. public long fileSize { get; set; }
  221. /// <summary>
  222. /// 物联网平台对设备上传同名文件的处理策略,默认为overwrite
  223. /// overwrite:覆盖模式 append:文件追加模式 reject:拒绝模式
  224. /// </summary>
  225. public string conflictStrategy { get; set; } = "overwrite";
  226. /// <summary>
  227. /// 文件的完整性校验模式,目前可取值crc64
  228. /// 若不传入,在文件上传完成后不校验文件完整性
  229. /// </summary>
  230. public string ficMode { get; set; }
  231. /// <summary>
  232. /// 文件的完整性校验值,是16位的Hex格式编码的字符串
  233. /// </summary>
  234. public string ficValue { get; set; }
  235. /// <summary>
  236. /// 自定义的设备请求上传文件的任务唯一ID,同一上传任务请求必须对应相同的唯一ID
  237. /// 不传入:物联网平台的云端识别当前请求为新的文件上传请求
  238. /// </summary>
  239. public string initUid { get; set; }
  240. /// <summary>
  241. /// 设备上传文件至OSS存储空间的配置参数
  242. /// </summary>
  243. public ConfigureParameters extraParams { get; set; } = new ConfigureParameters();
  244. }
  245. /// <summary>
  246. /// 配置参数
  247. /// </summary>
  248. public class ConfigureParameters
  249. {
  250. /// <summary>
  251. /// 设备上传文件的目标OSS所有者类型
  252. /// iot-platform:表示上传到阿里云物联网平台的OSS存储空间中
  253. /// device-user:表示上传到设备所属用户自己的OSS存储空间中
  254. /// </summary>
  255. public string ossOwnerType { get; set; } = "device-user";
  256. /// <summary>
  257. /// 文件上传相关的业务ID
  258. /// </summary>
  259. public string serviceId { get; set; }
  260. /// <summary>
  261. /// 标签
  262. /// </summary>
  263. public Dictionary<string, string> fileTag { get; set; }
  264. public ConfigureParameters()
  265. {
  266. }
  267. }
  268. #endregion
  269. #region 请求上传文件响应
  270. /// <summary>
  271. /// 请求响应
  272. /// </summary>
  273. public class FileUploadModelResult
  274. {
  275. /// <summary>
  276. /// 消息ID号
  277. /// 0~4294967295
  278. /// </summary>
  279. public string id { get; set; }
  280. /// <summary>
  281. /// 结果码。返回200表示成功
  282. /// </summary>
  283. public int code { get; set; }
  284. /// <summary>
  285. /// 请求失败时,返回的错误信息
  286. /// </summary>
  287. public string message { get; set; }
  288. /// <summary>
  289. /// 返回设备端的数据。
  290. /// </summary>
  291. public ResultData data { get; set; }
  292. }
  293. /// <summary>
  294. /// 请求响应数据
  295. /// </summary>
  296. public class ResultData
  297. {
  298. /// <summary>
  299. /// 设备上传文件的名称
  300. /// </summary>
  301. public string fileName { get; set; }
  302. /// <summary>
  303. /// 本次上传文件任务的标识ID。后续上传文件分片时,需要传递该文件标识ID。
  304. /// </summary>
  305. public string uploadId { get; set; }
  306. /// <summary>
  307. /// 仅当请求参数conflictStrategy为append,且物联网平台云端存在未完成上传的文件时,返回的已上传文件的大小,单位为字节。
  308. /// </summary>
  309. public long offset { get; set; }
  310. /// <summary>
  311. /// 当前上传文件分片的大小
  312. /// </summary>
  313. public long bSize { get; set; }
  314. /// <summary>
  315. /// 当上传了最后一个分片数据后,文件上传完成
  316. /// </summary>
  317. public bool complete { get; set; }
  318. /// <summary>
  319. /// 文件的完整性校验模式。若请求上传文件时传入了该参数,对应的值仅支持为crc64
  320. /// </summary>
  321. public string ficMode { get; set; }
  322. /// <summary>
  323. /// 文件上传完成,返回设备请求上传文件时的
  324. /// </summary>
  325. public string ficValueClient { get; set; }
  326. /// <summary>
  327. /// 文件上传完成,返回物联网平台云端计算的文件完整性校验值。该值与ficValueClient值相同,表示文件上传完整。
  328. /// </summary>
  329. public string ficValueServer { get; set; }
  330. }
  331. #endregion
  332. #region 上传文件
  333. public class FileSendModel
  334. {
  335. /// <summary>
  336. /// 消息ID号
  337. /// 0~4294967295
  338. /// </summary>
  339. public string id { get; set; }
  340. /// <summary>
  341. /// 上传业务参数
  342. /// </summary>
  343. public SendBusinessParameters @params { get; set; } = new SendBusinessParameters();
  344. public FileSendModel()
  345. {
  346. id = RandomHelper.GenerateRandomCode();
  347. }
  348. }
  349. /// <summary>
  350. /// 上传业务参数
  351. /// </summary>
  352. public class SendBusinessParameters
  353. {
  354. /// <summary>
  355. /// 设备请求上传文件时返回的文件上传任务标识ID。
  356. /// </summary>
  357. public string uploadId { get; set; }
  358. /// <summary>
  359. /// 已上传文件分片的总大小,单位为字节。
  360. /// </summary>
  361. public long offset { get; set; }
  362. /// <summary>
  363. /// 当前上传文件分片的大小,单位为字节
  364. /// </summary>
  365. public long bSize { get; set; }
  366. /// <summary>
  367. /// 仅当设备请求上传文件中fileSize为-1,即文件大小未知时,该参数有效,表示当前分片是否是文件的最后一个分片
  368. /// </summary>
  369. public bool isComplete { get; set; } = true;
  370. }
  371. #endregion
  372. #region 取消文件上传
  373. /// <summary>
  374. /// 取消上传
  375. /// </summary>
  376. public class CancelSend
  377. {
  378. /// <summary>
  379. /// 消息ID号
  380. /// 0~4294967295
  381. /// </summary>
  382. public string id { get; set; }
  383. /// <summary>
  384. /// 请求业务参数
  385. /// </summary>
  386. public object @params { get; set; }
  387. public CancelSend(string uploadId)
  388. {
  389. id = RandomHelper.GenerateRandomCode();
  390. @params = new { uploadId = uploadId };
  391. }
  392. }
  393. #endregion
  394. public class RandomHelper
  395. {
  396. /// <summary>
  397. ///生成制定位数的随机码(数字)
  398. /// </summary>
  399. /// <param name="length"></param>
  400. /// <returns></returns>
  401. public static string GenerateRandomCode(int length = 10)
  402. {
  403. var result = new StringBuilder();
  404. for (var i = 0; i < length; i++)
  405. {
  406. var r = new Random(Guid.NewGuid().GetHashCode());
  407. result.Append(r.Next(0, 10));
  408. }
  409. return result.ToString();
  410. }
  411. }
  412. }