XiaomiMESHttp_UpLoadFile.cs 19 KB

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