XiaomiMESHttp_UpLoadFile.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. using BZFAStandardLib;
  2. using Newtonsoft.Json;
  3. using Sunny.UI.Win32;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Net.Http;
  10. using System.Security.Cryptography;
  11. using System.Security.Policy;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using System.Windows.Forms;
  15. using static MainForm.ClassFile.XiaomiAPI.XiaomiMqttClient_Extend;
  16. using static MainForm.ClassFile.XiaomiAPI_MES.XiaomiMESHttp_StationInbound;
  17. using static MainForm.ClassFile.XiaomiAPI_MES.XiaomiMESHttp_StationOutbound.XmMES_StationOutRequest_Body;
  18. using static MainForm.ClassFile.XiaomiClass.MesHelper;
  19. namespace MainForm.ClassFile.XiaomiAPI_MES
  20. {
  21. /// <summary>
  22. /// 小米MES - 进站接⼝
  23. /// 接口地址:
  24. /// 接口方法:UnitConfirmDataSetIn
  25. /// </summary>
  26. public class XiaomiMESHttp_UpLoadFile : XiaomiMESHttp_X5
  27. {
  28. #region 变量
  29. /// <summary>
  30. /// 接口地址
  31. /// </summary>
  32. protected new static string UpFileUrl { set; get; } = GlobalContext.UpFileUrl;
  33. /// <summary>
  34. /// 接口方法
  35. /// </summary>
  36. protected new static string Method { set; get; } = "UploadMqtt";
  37. #endregion 变量
  38. /// <summary>
  39. /// 文件上传到 MES 系统
  40. /// </summary>
  41. /// <param name="wJPath">文件路径</param>
  42. /// <param name="fileUpload_X5">文件上传参数</param>
  43. /// <param name="fileMqttPayload">MQTT 负载参数</param>
  44. /// <returns>(状态码, 结果信息)</returns>
  45. public static async Task<(int, string)> FileUoladToMes(string wJPath, FileUpload_X5 fileUpload_X5, FileMqttPayload fileMqttPayload)
  46. {
  47. string logPath = GlobalContext.MqttFileUpLogDir + "FileInfo" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
  48. try
  49. {
  50. Stopwatch stopwatch1 = new Stopwatch();
  51. // 基础参数
  52. string url = "http://cm.pre.mi.com/file/x5/file/upload/mqtt";
  53. url = "http://im.pre.mi.com/file/x5/file/upload/mqtt";
  54. //这个之后要加到配置文件config中
  55. string appid = "Auto-Soft";
  56. //这个之后要加到配置文件config中
  57. string appkey = "5984710e-bb38-4806-b94d-7a9a727e3880";
  58. string method = "UploadMqtt";
  59. // 检查文件是否存在
  60. if (!System.IO.File.Exists(wJPath))
  61. {
  62. return (-1, "文件不存在");
  63. }
  64. // 获取文件信息
  65. FileInfo file = new FileInfo(wJPath);
  66. // 构造 body
  67. string body = BuildBody(fileUpload_X5, fileMqttPayload);
  68. // 计算 sign
  69. string sign = GetSign(appid + body + appkey); // MD5 加密
  70. // 构造 header
  71. Dictionary<string, string> header = BuildHeader(url, appid, method, sign);
  72. // 构造 data 参数
  73. Dictionary<string, object> dataParam = new Dictionary<string, object>
  74. {
  75. { "header", header },
  76. { "body", body }
  77. };
  78. // 将 data 参数序列化为 Base64 编码的字符串
  79. string data = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(dataParam)));
  80. FileOperate.NewTxtFile(logPath, DateTime.Now + $"==>【文件提交】开始上传:文件包[{fileUpload_X5.bucket}],文件名[{file.Name}]");
  81. stopwatch1.Start();
  82. // 调用上传方法
  83. var uploadResult = UploadFile(url, file, data).Result;
  84. stopwatch1.Stop();
  85. FileOperate.NewTxtFile(logPath, DateTime.Now+"===>【文件提交】HTTP提交:" + JsonConvert.SerializeObject(dataParam) + "\r\n==>提交结果:" + uploadResult + "");
  86. //获取返回参数
  87. FileUpload_Result fileUpList = JsonConvert.DeserializeObject<FileUpload_Result>(uploadResult);
  88. // 判断上传结果
  89. if (!string.IsNullOrEmpty(uploadResult) && !uploadResult.StartsWith("异常:") && !uploadResult.StartsWith("HTTP 错误:"))
  90. {
  91. //删除文件
  92. (bool, string) newResult = DeleteFile(wJPath);
  93. if (!newResult.Item1)
  94. {
  95. return (0, fileUpload_X5.name+newResult.Item2);
  96. }
  97. //附件信息添加到主页面
  98. FileData fileData = new FileData();
  99. fileData.FileName = file.Name;
  100. fileData.FileId = fileUpList.body.uuid;
  101. fileData.Bucket = fileUpList.body.bucket;
  102. Form_Home.fileUploadData.fileData.Add(fileData);
  103. FileOperate.NewTxtFile(logPath, DateTime.Now + $"==>【文件提交】上传结束:文件包[{fileUpload_X5.bucket}],文件名[{file.Name}],耗时[{stopwatch1.ElapsedMilliseconds}]");
  104. return (0, fileUpload_X5.name + $"文件上传成功");
  105. }
  106. else
  107. {
  108. FileOperate.NewTxtFile(logPath, DateTime.Now + $"==>【文件提交】上传失败:文件包[{fileUpload_X5.bucket}],文件名[{file.Name}],耗时[{stopwatch1.ElapsedMilliseconds}]");
  109. return (-2, fileUpload_X5.name+$"文件上传失败: {uploadResult}");
  110. }
  111. }
  112. catch (Exception ex)
  113. {
  114. return (-3, fileUpload_X5.name+$"发生异常: {ex.Message}");
  115. }
  116. }
  117. /// <summary>
  118. /// 将文件复制到目标文件夹,并删除源文件
  119. /// </summary>
  120. /// <param name="sourceFilePath">源文件路径</param>
  121. /// <param name="destinationFolderPath">目标文件夹路径</param>
  122. static string CopyAndDeleteFile(string sourceFilePath, string destinationFolderPath)
  123. {
  124. try
  125. {
  126. // 检查目标文件夹是否存在,如果不存在则创建
  127. if (!Directory.Exists(destinationFolderPath))
  128. {
  129. Directory.CreateDirectory(destinationFolderPath);
  130. }
  131. // 构建目标文件的完整路径
  132. string fileName = Path.GetFileName(sourceFilePath); // 获取源文件的文件名
  133. string destinationFilePath = Path.Combine(destinationFolderPath, fileName);
  134. // 如果目标文件已存在,则删除已有文件以避免冲突
  135. if (File.Exists(destinationFilePath))
  136. {
  137. File.Delete(destinationFilePath); // 删除已有文件
  138. }
  139. // 检查文件是否准备好
  140. if (!IsFileReady(sourceFilePath))
  141. {
  142. throw new IOException($"文件 {sourceFilePath} 正被其他进程占用,无法进行复制和删除操作。");
  143. }
  144. // 复制文件到目标文件夹
  145. File.Copy(sourceFilePath, destinationFilePath);
  146. // 删除源文件
  147. File.Delete(sourceFilePath);
  148. Console.WriteLine($"文件已从 {sourceFilePath} 成功复制到 {destinationFilePath} 并删除原文件。");
  149. // 返回目标文件路径
  150. return destinationFilePath;
  151. }
  152. catch (Exception ex)
  153. {
  154. Console.WriteLine($"文件转移失败: {ex.Message}");
  155. return null; // 或者返回空字符串,根据需求决定
  156. }
  157. }
  158. /// <summary>
  159. /// 删除文件
  160. /// </summary>
  161. /// <param name="sourceFilePath">文件路径</param>
  162. static (bool,string) DeleteFile(string sourceFilePath)
  163. {
  164. try
  165. {
  166. // 构建目标文件的完整路径
  167. string fileName = Path.GetFileName(sourceFilePath); // 获取源文件的文件名
  168. // 检查文件是否准备好
  169. if (!IsFileReady(sourceFilePath))
  170. {
  171. throw new IOException($"文件 {sourceFilePath} 正被其他进程占用,无法进行复制和删除操作。");
  172. }
  173. // 删除源文件
  174. File.Delete(sourceFilePath);
  175. return (true, "文件删除成功!");
  176. }
  177. catch (Exception ex)
  178. {
  179. return (false, "文件删除失败!"+ex.Message);
  180. }
  181. }
  182. public static bool IsFileReady(string filePath)
  183. {
  184. try
  185. {
  186. using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
  187. {
  188. stream.Close();
  189. }
  190. return true;
  191. }
  192. catch (IOException)
  193. {
  194. return false;
  195. }
  196. }
  197. /// <summary>
  198. /// 文件MD5加密
  199. /// </summary>
  200. /// <param name="file"></param>
  201. /// <returns></returns>
  202. public static string GetMD5Hex(FileInfo file)
  203. {
  204. using (MD5 md5 = MD5.Create())
  205. {
  206. using (FileStream stream = file.OpenRead())
  207. {
  208. byte[] hashBytes = md5.ComputeHash(stream);
  209. StringBuilder sb = new StringBuilder();
  210. for (int i = 0; i < hashBytes.Length; i++)
  211. {
  212. sb.Append(hashBytes[i].ToString("x2"));
  213. }
  214. return sb.ToString();
  215. }
  216. }
  217. }
  218. /// <summary>
  219. /// 数据pin接
  220. /// </summary>
  221. /// <param name="file"></param>
  222. /// <param name="fileMd5Hex"></param>
  223. /// <returns></returns>
  224. public static string BuildBody(FileUpload_X5 file, FileMqttPayload payload)
  225. {
  226. Dictionary<string, object> fileMetadata = new Dictionary<string, object>();
  227. Dictionary<string, string> mqttPayLoad = new Dictionary<string, string>();
  228. mqttPayLoad.Add("factory", payload.factory);
  229. mqttPayLoad.Add("project_name", payload.project_name);
  230. mqttPayLoad.Add("product_mode", payload.product_mode);
  231. mqttPayLoad.Add("line_no", payload.line_no);
  232. mqttPayLoad.Add("work_station_no", payload.work_station_no);
  233. mqttPayLoad.Add("equipment_no", payload.equipment_no);
  234. mqttPayLoad.Add("station_no", payload.station_no);
  235. mqttPayLoad.Add("file_id", payload.file_id);
  236. mqttPayLoad.Add("file_name", payload.file_name);
  237. mqttPayLoad.Add("sn", payload.sn);
  238. mqttPayLoad.Add("opt_time", payload.opt_time);
  239. mqttPayLoad.Add("file_type", payload.file_type);
  240. mqttPayLoad.Add("file_category", payload.file_category);
  241. mqttPayLoad.Add("tag", payload.tag);
  242. mqttPayLoad.Add("reference_info", Newtonsoft.Json.JsonConvert.SerializeObject(new object[] { new { pass_station_id = payload.reference_info.pass_station_id } }));
  243. fileMetadata.Add("bucket", file.bucket);
  244. fileMetadata.Add("name", file.name);
  245. fileMetadata.Add("uuid", file.uuid);
  246. fileMetadata.Add("md5", file.md5);
  247. fileMetadata.Add("uploadCloud", file.uploadCloud);
  248. fileMetadata.Add("informMqtt", file.informMqtt);
  249. fileMetadata.Add("mqttPayload", Newtonsoft.Json.JsonConvert.SerializeObject(mqttPayLoad));
  250. string body = Newtonsoft.Json.JsonConvert.SerializeObject(fileMetadata);
  251. return body;
  252. }
  253. /// <summary>
  254. /// 数据头拼接
  255. /// </summary>
  256. /// <param name="url"></param>
  257. /// <param name="appid"></param>
  258. /// <param name="method"></param>
  259. /// <param name="sign"></param>
  260. /// <returns></returns>
  261. public static Dictionary<string, string> BuildHeader(string url, string appid, string method, string sign)
  262. {
  263. Dictionary<string, string> header = new Dictionary<string, string>();
  264. header.Add("appid", appid);
  265. header.Add("method", method);
  266. header.Add("sign", sign);
  267. header.Add("url", url);
  268. return header;
  269. }
  270. public static string GetGuid()
  271. {
  272. return (System.Guid.NewGuid().ToString("N"));
  273. }
  274. /// <summary>
  275. /// 异步文件上传
  276. /// </summary>
  277. /// <param name="url">上传地址</param>
  278. /// <param name="file">文件路径</param>
  279. /// <param name="data">上传的数据</param>
  280. /// <returns>返回上传结果</returns>
  281. public static async Task<string> UploadFile(string url, FileInfo file, string data)
  282. {
  283. using (var httpClient = new HttpClient())
  284. {
  285. //httpClient.Timeout = new TimeSpan(80000000);
  286. var formData = new MultipartFormDataContent();
  287. // 添加文件
  288. var fileContent = new StreamContent(file.OpenRead());
  289. formData.Add(fileContent, "file", file.Name);
  290. // 添加数据
  291. formData.Add(new StringContent(data), "data");
  292. try
  293. {
  294. //var response = await httpClient.PostAsync(url, formData);
  295. var response = httpClient.PostAsync(url, formData).Result;
  296. if (response.IsSuccessStatusCode)
  297. {
  298. return await response.Content.ReadAsStringAsync();
  299. }
  300. else
  301. {
  302. return $"HTTP 错误: {response.StatusCode}";
  303. }
  304. }
  305. catch (Exception e)
  306. {
  307. return $"异常: {e.Message}";
  308. }
  309. }
  310. }
  311. /// <summary>
  312. /// MD5加密
  313. /// </summary>
  314. /// <param name="data"></param>
  315. /// <returns></returns>
  316. public static string GetSign(string data)
  317. {
  318. // 实例化一个md5对像
  319. MD5 md5 = MD5.Create();
  320. // MD5加密
  321. byte[] encodingMd5Data = md5.ComputeHash(Encoding.UTF8.GetBytes(data));
  322. // 生成签名字段
  323. string sign = "";
  324. for (int i = 0; i < encodingMd5Data.Length; i++)
  325. {
  326. // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符
  327. sign += encodingMd5Data[i].ToString("X2");
  328. }
  329. return sign;
  330. }
  331. public class FileUpload_X5
  332. {
  333. /// <summary>
  334. /// ⽂件所属包
  335. /// 必填
  336. /*
  337. ${file_category}/${file_type}/${项⽬号}/${⽣
  338. 产阶段}/${运⾏模式}/${过站结果}/${装备编
  339. 码}/${sn}/${pass_station_id}
  340. • 其中file_category的枚举值为:
  341. ◦ IMAGE
  342. ◦ TEXT
  343. • 若对应字段的值为空,则使⽤默认值
  344. UNKNOWN
  345. 注意:⾸位不能出现/,否则会出现路径错误
  346. 问题。
  347. 如:
  348. 正确⽰例
  349. IMAGE/IMAGE/N3/debug/online/PASS/MPA-0001/P320N000006B/382f55e9-c2bb
  350. */
  351. /// </summary>
  352. public string bucket { get; set; } = string.Empty;
  353. /// <summary>
  354. /// 文件名
  355. /// </summary>
  356. public string name { get; set; } = string.Empty;
  357. /// <summary>
  358. /// 文件唯一标识符
  359. /// </summary>
  360. public string uuid { get; set; } = string.Empty;
  361. /// <summary>
  362. /// md5 传空
  363. /// </summary>
  364. public string md5 { get; set; } = string.Empty;
  365. /// <summary>
  366. /// 是否上云 默认true
  367. /// </summary>
  368. public Boolean uploadCloud { get; set; }
  369. /// <summary>
  370. /// 是否通知Mqtt 默认true
  371. /// </summary>
  372. public Boolean informMqtt { get; set; }
  373. }
  374. public class FileMqttPayload
  375. {
  376. /// <summary>
  377. /// 工厂编码
  378. /// </summary>
  379. public string factory { get; set; } = string.Empty;
  380. /// <summary>
  381. /// 项目号
  382. /// </summary>
  383. public string project_name { get; set; } = string.Empty;
  384. /// <summary>
  385. /// 生产阶段
  386. /// </summary>
  387. public string product_mode { get; set; } = string.Empty;
  388. /// <summary>
  389. /// 线体
  390. /// </summary>
  391. public string line_no { get; set; } = string.Empty;
  392. /// <summary>
  393. /// 工站
  394. /// </summary>
  395. public string work_station_no { get; set; } = string.Empty;
  396. /// <summary>
  397. /// 装备
  398. /// </summary>
  399. public string equipment_no { get; set; } = string.Empty;
  400. /// <summary>
  401. /// 工位
  402. /// </summary>
  403. public string station_no { get; set; } = string.Empty;
  404. /// <summary>
  405. /// 文件ID
  406. /// </summary>
  407. public string file_id { get; set; } = string.Empty;
  408. /// <summary>
  409. /// 文件名
  410. /// </summary>
  411. public string file_name { get; set; } = string.Empty;
  412. /// <summary>
  413. /// 产品sn
  414. /// </summary>
  415. public string sn { get; set; } = string.Empty;
  416. /// <summary>
  417. /// 文件生成时间
  418. /// </summary>
  419. public string opt_time { get; set; } = string.Empty;
  420. /// <summary>
  421. /// 文件类型
  422. /// </summary>
  423. public string file_type { get; set; } = string.Empty;
  424. /// <summary>
  425. /// 文件类别
  426. /// </summary>
  427. public string file_category { get; set; } = string.Empty;
  428. /// <summary>
  429. /// 自定义标签信息
  430. /// </summary>
  431. public string tag { get; set; } = string.Empty;
  432. /// <summary>
  433. /// 关联业务信息
  434. /// </summary>
  435. public Reference_Info reference_info { get; set; }=new Reference_Info();
  436. }
  437. public class Reference_Info {
  438. public string pass_station_id { get; set; } = string.Empty;
  439. }
  440. /// <summary>
  441. /// 上传文件-返回参数
  442. /// </summary>
  443. public struct FileUpload_Result
  444. {
  445. public Header header { get; set; }
  446. public Body body { get; set; }
  447. public struct Header
  448. {
  449. public string code { get; set; }
  450. public string desc { get; set; }
  451. }
  452. public struct Body
  453. {
  454. public string uuid { get; set; }
  455. public string md5 { get; set; }
  456. public string bucket { get; set; }
  457. }
  458. }
  459. /// <summary>
  460. /// 待上传MES、Iot的文件信息
  461. /// </summary>
  462. public class FileUpload_FileData
  463. {
  464. public List<FileData> fileData { get; set; } = new List<FileData>();
  465. }
  466. public class FileData
  467. {
  468. /// <summary>
  469. /// ⽂件名称
  470. /// </summary>
  471. public string FileName { get; set; } = string.Empty;
  472. /// <summary>
  473. /// ⽂件在⽂件服务器对应的uuid,可以采⽤异步上传,⾃定义uuid,先上传out,然后再异步上传⽂件,提升过站效率
  474. /// </summary>
  475. public string FileId { get; set; } = string.Empty;
  476. /// <summary>
  477. /// ⽂件服务器bucket
  478. /// </summary>
  479. public string Bucket { get; set; } = string.Empty;
  480. }
  481. }
  482. }