fg_yida_2/YiDa_WinForm/Service/YiDaUploadService.cs
2026-02-05 14:45:33 +08:00

653 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using AlibabaCloud.SDK.Dingtalkoauth2_1_0.Models;
using MQTT_WinformV1;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Tea;
using YiDa_WinForm.Config;
using YiDa_WinForm.Model;
using YiDa_WinForm.Utils;
namespace YiDa_WinForm.Service
{
// 连杆上传状态事件参数(原有,保留不变)
public class RodUploadStatusEventArgs : EventArgs
{
/// <summary>
/// 是否上传成功(用于更新 MainForm 的 _isImageUploadedInCycle 标记)
/// </summary>
public bool IsUploadSuccess { get; set; }
/// <summary>
/// 附加消息(可选,如上传失败原因)
/// </summary>
public string Message { get; set; } = string.Empty;
}
public class YiDaUploadService
{
private readonly ButtonOperationService _buttonService;
// 数据库连接配置
private readonly string _connectionString = AppConfigHelper.MySqlConnectionString;
// 通知UI层
public event Action<string> MessageReceived;
// 钉钉token
static readonly string _tokenAppKey = AppConfigHelper.YiDa_TokenAppKey;
static readonly string _tokenAppSecret = AppConfigHelper.YiDa_TokenAppSecret;
// 宜搭上传配置
static readonly string _appType = AppConfigHelper.YiDa_AppType;
static readonly string _systemToken = AppConfigHelper.YiDa_SystemToken;
static readonly string _userId = AppConfigHelper.YiDa_UserId;
static readonly string _formUuid = AppConfigHelper.YiDa_FormUuid;
static readonly string _processCode = AppConfigHelper.YiDa_ProcessCode;
// 报废图片
public string _reformationPicture { get; set; } = string.Empty;
// 钉钉配置
static readonly string _baseAddress = AppConfigHelper.DingDing_BaseAddress;
static readonly string _dingDingWebHook = AppConfigHelper.DingDing_RobotWebHook;
static readonly string _dingDingSecret = AppConfigHelper.DingDing_RobotSecret;
// 定义公共事件(供 MainForm 订阅,连杆+报废)
public event EventHandler<ScrapUploadStatusEventArgs> ScrapUploadStatusChangedOne;
public event EventHandler<RodUploadStatusEventArgs> RodUploadStatusChangedOne;
public event EventHandler<ScrapUploadStatusEventArgs> ScrapUploadStatusChangedTwo;
public event EventHandler<RodUploadStatusEventArgs> RodUploadStatusChangedTwo;
// 构造函数(初始化 _buttonService
public YiDaUploadService()
{
_buttonService = new ButtonOperationService();
}
// ==================================== 宜搭上传(原有代码,保留不变) ====================================
private static AlibabaCloud.SDK.Dingtalkoauth2_1_0.Client CreateClient()
{
AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config();
config.Protocol = "https";
config.RegionId = "central";
return new AlibabaCloud.SDK.Dingtalkoauth2_1_0.Client(config);
}
public string GetDingDingToken()
{
string strToken = string.Empty;
GetAccessTokenResponse token = null;
var client = CreateClient();
var getAccessTokenRequest = new GetAccessTokenRequest
{
AppKey = _tokenAppKey,
AppSecret = _tokenAppSecret,
};
try
{
token = client.GetAccessToken(getAccessTokenRequest);
}
catch (TeaException err)
{
string errorMsg = $"获取钉钉Token异常【TeaException】错误码={err.Code},错误信息={err.Message}";
Console.WriteLine(errorMsg);
LogHelper.AppendLog(errorMsg);
}
catch (Exception ex)
{
string errorMsg = $"获取 token 异常:{ex.Message}";
Console.WriteLine(errorMsg);
LogHelper.AppendLog(errorMsg);
}
return token != null ? token.Body.AccessToken : string.Empty;
}
private static async Task RecordSuccessLog(List<MqttModel> mqttLists)
{
if (mqttLists != null && mqttLists.Count > 0)
{
var buttonService = new ButtonOperationService();
await buttonService.CreateSuccessLog(mqttLists);
}
}
private static AlibabaCloud.SDK.Dingtalkyida_2_0.Client CreateYiDaClient()
{
AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config();
config.Protocol = "https";
config.RegionId = "central";
return new AlibabaCloud.SDK.Dingtalkyida_2_0.Client(config);
}
public async Task UploadDatabaseDataToYiDaWithLogging(string accessToken, List<YiDaModel> yidaLists,
List<MqttModel> mqttLists, MainForm form)
{
var client = CreateYiDaClient();
AlibabaCloud.SDK.Dingtalkyida_2_0.Models.StartInstanceHeaders startInstanceHeaders =
new AlibabaCloud.SDK.Dingtalkyida_2_0.Models.StartInstanceHeaders();
startInstanceHeaders.XAcsDingtalkAccessToken = accessToken;
for (int i = 0; i < yidaLists.Count; i++)
{
var item = yidaLists[i];
// var mqttItem = mqttLists[i];
var dataDict = new Dictionary<string, object>
{
{ "textField_mha98neu", item.textField_mha98neu }, //供应商代码
{ "textField_mha98nev", item.textField_mha98nev }, //供应商名称
{ "textField_mha98new", item.textField_mha98new }, //车型
{ "textField_mha98nex", item.textField_mha98nex }, //零件号
{ "textField_mha98ney", item.textField_mha98ney }, //零件名称
{ "textField_mha98nf1", item.textField_mha98nf1 }, //参数名
{ "textField_mhx44i2i", item.textField_mhx44i2i }, //参数值
{ "textField_mhx44i2j", item.textField_mhx44i2j }, //下公差
{ "textField_mhx44i2k", item.textField_mhx44i2k }, //上公差
{ "textField_mha98nf7", item.textField_mha98nf7 }, //零件责任人
{ "textField_mhlvt8ht", DateTime.Now.ToString("yyyy-MM-dd") }, //写入时间
{ "textField_mha98nf5", item.textField_mha98nf5 }, //合格判断
{ "textField_mha98nf0", item.textField_mha98nf0 }, //工位
{ "textField_mha98nfh", item.textField_mha98nfh } //外保负责人
};
string jsonData = JsonConvert.SerializeObject(dataDict);
AlibabaCloud.SDK.Dingtalkyida_2_0.Models.StartInstanceRequest startInstanceRequest =
new AlibabaCloud.SDK.Dingtalkyida_2_0.Models.StartInstanceRequest
{
AppType = _appType,
SystemToken = _systemToken,
UserId = _userId,
Language = "zh_CN",
FormUuid = _formUuid,
FormDataJson = jsonData,
ProcessCode = _processCode,
};
try
{
var response = client.StartInstanceWithOptions(startInstanceRequest, startInstanceHeaders,
new AlibabaCloud.TeaUtil.Models.RuntimeOptions());
if (response != null && response.Body != null)
{
await RecordSuccessLog(mqttLists);
string logData = JsonConvert.SerializeObject(dataDict, Formatting.Indented);
LogHelper.AppendLog($"上传数据成功:\r\n{logData}");
Console.WriteLine($"上传数据成功:\r\n{logData}");
}
else
{
Console.WriteLine($"上传返回空结果,数据未存入 MySQL。");
string logData = JsonConvert.SerializeObject(dataDict, Formatting.Indented);
LogHelper.AppendLog($"上传返回空结果,数据未存入 MySQL:\r\n{logData}");
}
}
catch (TeaException err)
{
Console.WriteLine($"上传异常:{err.Code} --- {err.Message}");
string logData = JsonConvert.SerializeObject(dataDict, Formatting.Indented);
LogHelper.AppendLog($"上传异常:{err.Code} --- {err.Message}\r\n数据:\r\n{logData}");
}
catch (Exception ex)
{
Console.WriteLine($"上传异常:{ex.Message}");
string logData = JsonConvert.SerializeObject(dataDict, Formatting.Indented);
LogHelper.AppendLog($"上传异常:{ex.Message}\r\n数据:\r\n{logData}");
}
}
}
// ==================================== 核心修复签名生成方法补充URL编码 ====================================
static string CalcSign(long timestamp, string secret)
{
if (string.IsNullOrWhiteSpace(secret))
{
throw new ArgumentNullException(nameof(secret), "钉钉机器人密钥不能为空");
}
string stringToSign = $"{timestamp}\n{secret}";
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
string base64Sign = Convert.ToBase64String(hashBytes);
return base64Sign;
}
// ==================================== 报废证据上传 ====================================
public async Task UploadScrapCertificate(string token, string filePath, string deviceCode)
{
try
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
string errorMsg = "报废图片路径为空或文件不存在";
if (deviceCode.Contains("1"))
{
OnScrapUploadStatusChangedOne(false, errorMsg);
}
else
{
OnScrapUploadStatusChangedTwo(false, errorMsg);
}
return;
}
// 使用using包裹HttpClient和FileStream
using (HttpClient client = new HttpClient { BaseAddress = new Uri(_baseAddress) })
{
using (var form = new MultipartFormDataContent())
{
using (var fileStream = File.OpenRead(filePath)) // 关键:释放文件句柄
{
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
form.Add(streamContent, "media", Path.GetFileName(filePath));
form.Add(new StringContent("image"), "type");
var url = $"/media/upload?access_token={token}";
var resp = await client.PostAsync(url, form);
resp.EnsureSuccessStatusCode();
string json = await resp.Content.ReadAsStringAsync();
JObject jo = JObject.Parse(json);
string mediaId = jo["media_id"].Value<string>();
LogHelper.AppendLog($"报废图片上传完成mediaId = {mediaId}");
// 3. 发送群消息
long ts = DateTimeOffset.Now.ToUnixTimeMilliseconds();
string ymdhms = DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss");
string sign = CalcSign(ts, _dingDingSecret);
string webhook_url = $"{_dingDingWebHook}&timestamp={ts}&sign={sign}";
var msg = new
{
msgtype = "markdown",
markdown = new
{
title = "报废证据上传",
text = $"## 报废凭证照片\n<img src=\"{mediaId}\" width=\"200\" />\n\n> 时间:{ymdhms}"
}
};
string transJson = JsonConvert.SerializeObject(msg);
var resp2 = await client.PostAsync(webhook_url,
new StringContent(transJson, Encoding.UTF8, "application/json"));
resp2.EnsureSuccessStatusCode();
string json2 = await resp2.Content.ReadAsStringAsync();
JObject res2 = JObject.Parse(json2);
string errmsg = res2["errmsg"].Value<string>();
LogHelper.AppendLog($"报废图片发送群消息完成msg = {errmsg}");
}
}
}
}
catch (Exception ex)
{
string errorMsg = $"报废凭证图片上传异常:{ex.Message}";
LogHelper.AppendLog(errorMsg);
throw;
}
}
// ==================================== 连杆测试上传 ====================================
public async Task ConnectingRodTestUpload(string token, string filePath, string deviceCode)
{
try
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
string errorMsg = "连杆图片路径为空或文件不存在";
if (deviceCode.Contains("1"))
{
OnRodUploadStatusChangedOne(false, errorMsg);
}
else
{
OnRodUploadStatusChangedTwo(false, errorMsg);
}
return;
}
// 1. 初始化
HttpClient client = new HttpClient
{
BaseAddress = new Uri(_baseAddress)
};
var form = new MultipartFormDataContent();
var fileStream = File.OpenRead(filePath);
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg"); // 或 image/png
form.Add(streamContent, "media", Path.GetFileName(filePath));
form.Add(new StringContent("image"), "type"); // 接口要求字段 type=image
// 2. 发送请求上传文件access_token 放查询串)
var url = $"/media/upload?access_token={token}";
var resp = await client.PostAsync(url, form);
resp.EnsureSuccessStatusCode();
string json = await resp.Content.ReadAsStringAsync();
JObject jo = JObject.Parse(json);
string mediaId = jo["media_id"].Value<string>();
LogHelper.AppendLog($"连杆图片上传完成mediaId = {mediaId}");
// 3. 发送群消息
long ts = DateTimeOffset.Now.ToUnixTimeMilliseconds();
string ymdhms = DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss");
string sign = CalcSign(ts, _dingDingSecret); // HMACSHA256→Base64
string webhook_url = $"{_dingDingWebHook}&timestamp={ts}&sign={sign}";
var msg = new
{
msgtype = "markdown",
markdown = new
{
title = "连杆测试上传",
text = $"## 连杆测试照片\n<img src=\"{mediaId}\" width=\"200\" />\n\n> 时间:{ymdhms}"
}
};
string transJson = JsonConvert.SerializeObject(msg);
var resp2 = await client.PostAsync(webhook_url,
new StringContent(transJson, Encoding.UTF8, "application/json"));
resp2.EnsureSuccessStatusCode();
string json2 = await resp2.Content.ReadAsStringAsync();
JObject res2 = JObject.Parse(json2);
string errmsg = res2["errmsg"].Value<string>();
LogHelper.AppendLog($"连杆图片发送群消息完成msg = {errmsg}");
}
catch (Exception ex)
{
string errorMsg = $"连杆测试图片上传异常:{ex.Message}";
LogHelper.AppendLog(errorMsg);
throw;
}
}
// ==================================== 钉钉报警 ====================================
public async Task<bool> SendDingDingTextAlarmAsync(string alarmContent, string deviceCode, bool isRodAlarm = true)
{
// 前置校验
if (string.IsNullOrEmpty(alarmContent))
{
LogHelper.AppendLog("钉钉报警失败:报警内容不能为空");
return false;
}
// 初始化 HttpClient
HttpClient client = new HttpClient
{
BaseAddress = new Uri(_baseAddress)
};
try
{
// 4. 生成加签参数
long ts = DateTimeOffset.Now.ToUnixTimeMilliseconds();
string ymdhms = DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss");
string sign = CalcSign(ts, _dingDingSecret);
string webhook_url = $"{_dingDingWebHook}&timestamp={ts}&sign={sign}";
string alarmTitle;
string alarmDesc;
// 5. 构造报警消息(区分连杆/报废)
if (deviceCode.Contains("1"))
{
alarmTitle = isRodAlarm ? "设备1连杆测试报警" : "设备1报废上传报警";
alarmDesc = isRodAlarm ? "未及时提供强度测试图片" : "未及时提供报废图片";
}
else
{
alarmTitle = isRodAlarm ? "设备2连杆测试报警" : "设备2报废上传报警";
alarmDesc = isRodAlarm ? "未及时提供强度测试图片" : "未及时提供报废图片";
}
var msg = new
{
msgtype = "markdown",
markdown = new
{
title = alarmTitle,
text = $"## **{alarmTitle}**<br>报警内容:{alarmContent}"
}
};
// 6. 序列化消息
string transJson = JsonConvert.SerializeObject(msg);
LogHelper.AppendLog($"钉钉报警:构造的 JSON 消息:\r\n{transJson}");
// 7. 发送请求
var resp2 = await client.PostAsync(webhook_url,
new StringContent(transJson, Encoding.UTF8, "application/json"));
resp2.EnsureSuccessStatusCode();
// 8. 解析响应结果
string json2 = await resp2.Content.ReadAsStringAsync();
JObject res2 = JObject.Parse(json2);
string errmsg = res2["errmsg"].Value<string>();
LogHelper.AppendLog($"钉钉报警发送完成msg = {errmsg}");
// 9. 判断是否真正成功
return errmsg.Equals("ok", StringComparison.OrdinalIgnoreCase);
}
catch (Exception ex)
{
LogHelper.AppendLog($"钉钉报警异常:{ex.Message},堆栈信息:{ex.StackTrace}");
return false;
}
finally
{
// 10. 释放资源
client.Dispose();
}
}
// ==================================== 报废上传内部封装 + 事件触发 ====================================
/// <summary>
/// 报废上传设备1
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async Task<bool> UploadScrapDeviceOneAsync(string filePath)
{
try
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
string errorMsg = "图片路径为空或文件不存在";
OnScrapUploadStatusChangedOne(false, errorMsg);
return false;
}
string token = GetDingDingToken();
if (string.IsNullOrEmpty(token))
{
string errorMsg = "获取 DingDing Token 失败,无法执行上传";
OnScrapUploadStatusChangedOne(false, errorMsg);
return false;
}
await UploadScrapCertificate(token, filePath, "1");
string successMsg = "报废凭证图片上传成功,已发送至钉钉群";
OnScrapUploadStatusChangedOne(true, successMsg);
return true;
}
catch (Exception ex)
{
string errorMsg = $"报废凭证图片上传异常:{ex.Message}";
LogHelper.AppendLog(errorMsg);
OnScrapUploadStatusChangedOne(false, errorMsg);
return false;
}
}
/// <summary>
/// 报废上传设备2
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async Task<bool> UploadScrapDeviceTwoAsync(string filePath)
{
try
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
string errorMsg = "图片路径为空或文件不存在";
OnScrapUploadStatusChangedTwo(false, errorMsg);
return false;
}
string token = GetDingDingToken();
if (string.IsNullOrEmpty(token))
{
string errorMsg = "获取 DingDing Token 失败,无法执行上传";
OnScrapUploadStatusChangedTwo(false, errorMsg);
return false;
}
await UploadScrapCertificate(token, filePath, "2");
string successMsg = "报废凭证图片上传成功,已发送至钉钉群";
OnScrapUploadStatusChangedTwo(true, successMsg);
return true;
}
catch (Exception ex)
{
string errorMsg = $"报废凭证图片上传异常:{ex.Message}";
LogHelper.AppendLog(errorMsg);
OnScrapUploadStatusChangedTwo(false, errorMsg);
return false;
}
}
// ==================================== 连杆上传内部封装 + 事件触发 ====================================
/// <summary>
/// 连杆上传设备1
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async Task<bool> UploadRodTestDeviceOneAsync(string filePath)
{
try
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
string errorMsg = "图片路径为空或文件不存在";
OnRodUploadStatusChangedOne(false, errorMsg);
return false;
}
string token = GetDingDingToken();
if (string.IsNullOrEmpty(token))
{
string errorMsg = "获取 DingDing Token 失败,无法执行上传";
OnRodUploadStatusChangedOne(false, errorMsg);
return false;
}
await ConnectingRodTestUpload(token, filePath, "1");
string successMsg = "连杆测试图片上传成功,已发送至钉钉群";
OnRodUploadStatusChangedOne(true, successMsg);
return true;
}
catch (Exception ex)
{
string errorMsg = $"连杆测试图片上传异常:{ex.Message}";
LogHelper.AppendLog(errorMsg);
OnRodUploadStatusChangedOne(false, errorMsg);
return false;
}
}
/// <summary>
/// 连杆上传设备1
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async Task<bool> UploadRodTestDeviceTwoAsync(string filePath)
{
try
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
string errorMsg = "图片路径为空或文件不存在";
OnRodUploadStatusChangedTwo(false, errorMsg);
return false;
}
string token = GetDingDingToken();
if (string.IsNullOrEmpty(token))
{
string errorMsg = "获取 DingDing Token 失败,无法执行上传";
OnRodUploadStatusChangedTwo(false, errorMsg);
return false;
}
await ConnectingRodTestUpload(token, filePath, "2");
string successMsg = "连杆测试图片上传成功,已发送至钉钉群";
OnRodUploadStatusChangedTwo(true, successMsg);
return true;
}
catch (Exception ex)
{
string errorMsg = $"连杆测试图片上传异常:{ex.Message}";
LogHelper.AppendLog(errorMsg);
OnRodUploadStatusChangedTwo(false, errorMsg);
return false;
}
}
// ==================================== 事件触发辅助方法(连杆+报废) ====================================
protected virtual void OnScrapUploadStatusChangedOne(bool isUploadSuccess, string message)
{
ScrapUploadStatusChangedOne?.Invoke(this, new ScrapUploadStatusEventArgs
{
IsUploadSuccess = isUploadSuccess,
Message = message
});
}
protected virtual void OnRodUploadStatusChangedOne(bool isUploadSuccess, string message)
{
RodUploadStatusChangedOne?.Invoke(this, new RodUploadStatusEventArgs
{
IsUploadSuccess = isUploadSuccess,
Message = message
});
}
protected virtual void OnScrapUploadStatusChangedTwo(bool isUploadSuccess, string message)
{
ScrapUploadStatusChangedTwo?.Invoke(this, new ScrapUploadStatusEventArgs
{
IsUploadSuccess = isUploadSuccess,
Message = message
});
}
protected virtual void OnRodUploadStatusChangedTwo(bool isUploadSuccess, string message)
{
RodUploadStatusChangedTwo?.Invoke(this, new RodUploadStatusEventArgs
{
IsUploadSuccess = isUploadSuccess,
Message = message
});
}
}
}