diff --git a/ZR.Common/MqttHelper/MqttService.cs b/ZR.Common/MqttHelper/MqttService.cs index bbe25367..50c2c15c 100644 --- a/ZR.Common/MqttHelper/MqttService.cs +++ b/ZR.Common/MqttHelper/MqttService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; @@ -164,10 +165,19 @@ namespace ZR.Common.MqttHelper } } - private Task OnConnectedAsync(MqttClientConnectedEventArgs e) + private async Task OnConnectedAsync(MqttClientConnectedEventArgs e) { - _logger.LogInformation("MQTT连接已建立"); - return SubscribeToTopicsAsync(); + _logger.LogInformation($"MQTT连接已建立,会话是否存在: {e.ConnectResult.IsSessionPresent}"); + + // 仅在会话不存在时订阅(首次连接或会话失效) + if (!e.ConnectResult.IsSessionPresent) + { + await SubscribeToTopicsAsync(); + } + else + { + _logger.LogInformation("会话已存在,服务器保留订阅状态,跳过订阅"); + } } private async Task OnDisconnectedAsync(MqttClientDisconnectedEventArgs e) @@ -228,43 +238,47 @@ namespace ZR.Common.MqttHelper } } + private readonly HashSet _subscribedTopics = new HashSet(); // 线程安全可加锁 + private async Task SubscribeToTopicsAsync() { if (!_mqttClient.IsConnected) { - _logger.LogWarning("无法订阅主题:MQTT客户端未连接"); + _logger.LogWarning("无法订阅:客户端未连接"); return; } - try + var subscribeOptions = _mqttConfig.GetSubscribeToTopicsAsync(); + if (subscribeOptions == null || !subscribeOptions.TopicFilters.Any()) { - // 获取配置类生成的订阅选项 - var subscribeOptions = _mqttConfig.GetSubscribeToTopicsAsync(); - - if (subscribeOptions == null || !subscribeOptions.TopicFilters.Any()) - { - _logger.LogInformation("没有配置要订阅的MQTT主题"); - return; - } - - _logger.LogInformation( - $"正在订阅MQTT主题: {string.Join(", ", subscribeOptions.TopicFilters.Select(f => f.Topic))}" - ); - - // 使用强类型的订阅选项进行订阅 - var result = await _mqttClient.SubscribeAsync(subscribeOptions); - - foreach (var item in result.Items) - { - _logger.LogInformation( - $"订阅主题 '{item.TopicFilter.Topic}' 结果: {item.ResultCode}" - ); - } + _logger.LogInformation("无订阅主题配置"); + return; } - catch (Exception ex) + + // 提取需要订阅的主题 + var topicsToSubscribe = subscribeOptions.TopicFilters.Select(f => f.Topic).ToList(); + + // 检查是否已订阅所有主题,避免重复 + if (_subscribedTopics.SetEquals(topicsToSubscribe)) { - _logger.LogError(ex, "订阅MQTT主题时出错"); - ScheduleReconnect(); + _logger.LogInformation("所有主题已订阅,跳过订阅操作"); + return; + } + + // 执行订阅并更新本地状态 + _logger.LogInformation($"订阅主题: {string.Join(", ", topicsToSubscribe)}"); + var result = await _mqttClient.SubscribeAsync(subscribeOptions); + + // 更新本地已订阅主题列表 + lock (_subscribedTopics) + { + _subscribedTopics.Clear(); + _subscribedTopics.UnionWith(topicsToSubscribe); + } + + foreach (var item in result.Items) + { + _logger.LogInformation($"订阅结果:{item.TopicFilter.Topic} -> {item.ResultCode}"); } } @@ -396,7 +410,6 @@ namespace ZR.Common.MqttHelper _logger.LogError(ex, "清理MQTT客户端资源时出错"); } } - public void Dispose() { Dispose(true); diff --git a/ZR.Common/MqttHelper/MyMqttConfig.cs b/ZR.Common/MqttHelper/MyMqttConfig.cs index 4d85b4e3..7ddcb2f0 100644 --- a/ZR.Common/MqttHelper/MyMqttConfig.cs +++ b/ZR.Common/MqttHelper/MyMqttConfig.cs @@ -40,7 +40,7 @@ namespace ZR.Common.MqttHelper var builder = new MqttClientOptionsBuilder() .WithClientId(mqttConfig["ClientId"] ?? Guid.NewGuid().ToString()) .WithTcpServer(mqttConfig["Server"], mqttConfig.GetValue("Port", 1883)) - .WithCleanSession(); + .WithCleanSession(false); return builder.Build(); } diff --git a/ZR.Service/mes/qc/backend/QcBackEndService.cs b/ZR.Service/mes/qc/backend/QcBackEndService.cs index e53db073..fbcb5cb8 100644 --- a/ZR.Service/mes/qc/backend/QcBackEndService.cs +++ b/ZR.Service/mes/qc/backend/QcBackEndService.cs @@ -1,17 +1,18 @@ -using System; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using Infrastructure.Attribute; using Microsoft.Extensions.Logging; using MQTTnet.Protocol; using SqlSugar; +using System; +using System.Collections.Concurrent; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using ZR.Common.MqttHelper; using ZR.Model.Business; using ZR.Model.Dto; using ZR.Model.MES.wms; using ZR.Service.Business.IBusinessService; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace ZR.Service.Business { @@ -974,7 +975,8 @@ namespace ZR.Service.Business ); var payload = JsonSerializer.Serialize(mqttEventDto); - + // 添加打印记录 + await AddBackendLabelPrintRecordAsync(mqttEventDto, newLabelScran.WorkOrder, maxPackage, specialPrintType); // 保持原有PublishAsync调用方式 await _mqttService.PublishAsync( topic, @@ -985,8 +987,7 @@ namespace ZR.Service.Business _logger.LogInformation($"发送后道外箱标签打印成功:{topic}"); - // 添加打印记录 - await AddBackendLabelPrintRecordAsync(mqttEventDto, newLabelScran.WorkOrder, maxPackage, specialPrintType); + } catch (JsonException ex) { @@ -1025,7 +1026,7 @@ namespace ZR.Service.Business _logger.LogWarning(ex, "解析批次号失败"); } // 上一个内标签流水号检查 - int oldPackageLabelSort = 1; +/* int oldPackageLabelSort = 1; QcBackendRecordLabelPrint lastPackagelabelInfo = Context .Queryable() .Where(it => it.PartNumber == labelScan.PartNumber) @@ -1040,7 +1041,7 @@ namespace ZR.Service.Business else { oldPackageLabelSort = lastPackagelabelInfo.SerialNumber.Value; - } + }*/ QcBackendRecordLabelPrint printRecord = new() { @@ -1052,7 +1053,7 @@ namespace ZR.Service.Business Description = description, Team = labelScan.Team ?? "未知班组", BatchCode = batchCode, - SerialNumber = oldPackageLabelSort + 1, + SerialNumber = labelScan.Sort, PartNum = maxPackage, LabelType = 1, BoxMaxNum = maxPackage, diff --git a/ZR.Service/mes/wms-u8/ERP_WMS_interactiveService.cs b/ZR.Service/mes/wms-u8/ERP_WMS_interactiveService.cs index cdb873b9..a2b59791 100644 --- a/ZR.Service/mes/wms-u8/ERP_WMS_interactiveService.cs +++ b/ZR.Service/mes/wms-u8/ERP_WMS_interactiveService.cs @@ -1,16 +1,16 @@ using Infrastructure; using Infrastructure.Attribute; using Newtonsoft.Json; +using NLog; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using U8Server.Util; using ZR.Model.MES.wms; -using ZR.Service.mes.wms.IService; using ZR.Service.mes.wms_u8.IService; namespace ZR.Service.mes.wms_u8 @@ -18,129 +18,295 @@ namespace ZR.Service.mes.wms_u8 [AppService(ServiceType = typeof(IERP_WMS_interactive), ServiceLifetime = LifeTime.Transient)] public class ERP_WMS_interactiveService : IERP_WMS_interactive { - /// - /// 入库接口 - /// - /// - /// - /// - public ERP_WMS_interactiveModelResult Inbounded(string urlBase, List eRP_WMS_InteractiveModels) + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + // 假设接口密钥(需根据实际文档填写) + //private const string ApiSecret = "your_api_secret"; // 替换为实际密钥 + + #region 同步方法 + public ERP_WMS_interactiveModelResult Inbounded(string urlBase, List models) { - string url = urlBase + "/wms/mes/inbounded"; - - string contentType = "application/json"; - Dictionary headers = new Dictionary(); - headers.Add("appid", "gN9yId!!lfwaRoi3"); - headers.Add("timestamp", DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)); - headers.Add("sign", GetSign.GetBy16Md5()); - - string postData = JsonConvert.SerializeObject(eRP_WMS_InteractiveModels); - Object result = HttpHelper.HttpPost(url, postData, contentType, 5, headers); - if (result != null && result is ERP_WMS_interactiveModelResult) - { - return (ERP_WMS_interactiveModelResult)result; - } - - return null; - + return ProcessSyncRequest(urlBase, models, "inbounded", isInbound: true); } - /// - /// 出库接口 - /// - /// - /// - /// - public ERP_WMS_interactiveModelResult Outbounded(string urlBase, List eRP_WMS_InteractiveModels) + public ERP_WMS_interactiveModelResult Outbounded(string urlBase, List models) { - string url = urlBase + "/wms/mes/outbounded"; + return ProcessSyncRequest(urlBase, models, "outbounded", isInbound: false); + } + #endregion - string contentType = "application/json"; - Dictionary headers = new Dictionary(); - headers.Add("appid", "gN9yId!!lfwaRoi3"); - headers.Add("timestamp", DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)); - headers.Add("sign", GetSign.GetBy16Md5()); - - string postData = JsonConvert.SerializeObject(eRP_WMS_InteractiveModels); - Object result = HttpHelper.HttpPost(url, postData, contentType, 5, headers); - if (result != null && result is ERP_WMS_interactiveModelResult) - { - return (ERP_WMS_interactiveModelResult)result; - } - - return null; + #region 异步方法 + public async Task InboundedAsync(string urlBase, List models) + { + return await ProcessAsyncRequest(urlBase, models, "inbounded", isInbound: true); } - /// - /// 入库接口 - 异步版本 - /// - public async Task InboundedAsync(string urlBase, List eRP_WMS_InteractiveModels) + public async Task OutboundedAsync(string urlBase, List models) { - string url = urlBase + "/wms/mes/inbounded"; + return await ProcessAsyncRequest(urlBase, models, "outbounded", isInbound: false); + } + #endregion - string contentType = "application/json"; - Dictionary headers = new Dictionary + #region 公共处理方法 + /// + /// 同步请求处理(抽取公共逻辑) + /// + private ERP_WMS_interactiveModelResult ProcessSyncRequest(string urlBase, List models, string action, bool isInbound) + { + var operation = isInbound ? "入库" : "出库"; + _logger.Info($"开始处理{operation}请求 - URL基础: {urlBase}, 记录数: {models?.Count ?? 0}"); + + // 1. 基础参数校验 + if (!ValidateBaseParams(urlBase, models, operation, out var errorMsg)) { - { "appid", "gN9yId!!lfwaRoi3" }, - { "timestamp", DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) }, - { "sign", GetSign.GetBy16Md5() } - }; + _logger.Error($"{operation}请求失败: {errorMsg}"); + return null; + } - string postData = JsonConvert.SerializeObject(eRP_WMS_InteractiveModels); + // 2. 构建URL和请求数据 + string url = BuildUrl(urlBase, action); + string requestData = JsonConvert.SerializeObject(models); + _logger.Debug($"{operation}请求数据: {requestData}"); + + // 3. 构建headers(含签名) + var headers = BuildHeaders(requestData); try { - // 调用HttpHelper的异步方法 - string resultJson = await HttpHelper.HttpPostAsync(url, postData, contentType, 5, headers); + _logger.Trace($"发送{operation}同步HTTP请求 - URL: {url}"); + object result = HttpHelper.HttpPost(url, requestData, "application/json", 5, headers); - // 反序列化结果 - if (!string.IsNullOrEmpty(resultJson) && resultJson != "异常:*") - { - return JsonConvert.DeserializeObject(resultJson); - } + // 4. 处理响应(同步方法假设HttpPost返回已反序列化对象,需根据实际HttpHelper调整) + return ProcessSyncResponse(result, operation, url); } catch (Exception ex) { - Console.WriteLine($"InboundedAsync 异常: {ex.Message}"); + return HandleException(ex, operation, url, requestData); + } + } + + /// + /// 异步请求处理(抽取公共逻辑) + /// + private async Task ProcessAsyncRequest(string urlBase, List models, string action, bool isInbound) + { + var operation = isInbound ? "异步入库" : "异步出库"; + _logger.Info($"开始处理{operation}请求 - URL基础: {urlBase}, 记录数: {models?.Count ?? 0}"); + + // 1. 基础参数校验 + if (!ValidateBaseParams(urlBase, models, operation, out var errorMsg)) + { + _logger.Error($"{operation}请求失败: {errorMsg}"); + return null; } + // 2. 构建URL和请求数据 + string url = BuildUrl(urlBase, action); + string requestData = JsonConvert.SerializeObject(models); + _logger.Debug($"{operation}请求数据: {requestData}"); + + // 3. 构建headers(含签名) + var headers = BuildHeaders(requestData); + + try + { + _logger.Trace($"发送{operation}异步HTTP请求 - URL: {url}"); + string resultJson = await HttpHelper.HttpPostAsync(url, requestData, "application/json", 5, headers); + + // 4. 处理响应(先校验JSON格式,再反序列化) + return await ProcessAsyncResponse(resultJson, operation, url); + } + catch (Exception ex) + { + return HandleException(ex, operation, url, requestData); + } + } + #endregion + + #region 工具方法 + /// + /// 构建URL(避免双斜杠问题) + /// + private string BuildUrl(string urlBase, string action) + { + // 移除urlBase结尾的斜杠,再拼接路径 + return $"{urlBase.TrimEnd('/')}/wms/mes/{action}"; + } + + /// + /// 构建请求头(含签名生成) + /// + private Dictionary BuildHeaders(string requestData) + { + string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture); + string appid = "gN9yId!!lfwaRoi3"; + + // 签名生成规则:通常为appid + timestamp + requestData + secret的MD5(需根据接口文档调整) + //string signSource = $"{appid}{timestamp}{requestData}{ApiSecret}"; + string sign = GetSign.GetBy16Md5(); // 修正签名生成逻辑 + + return new Dictionary + { + { "appid", appid }, + { "timestamp", timestamp }, + { "sign", sign }, + { "Content-Type", "application/json" } + }; + } + + /// + /// 基础参数校验 + /// + private bool ValidateBaseParams(string urlBase, List models, string operation, out string errorMsg) + { + if (string.IsNullOrEmpty(urlBase)) + { + errorMsg = "基础URL为空"; + return false; + } + + if (models == null || !models.Any()) + { + errorMsg = "请求数据为空"; + return false; + } + + errorMsg = string.Empty; + return true; + } + + /// + /// 处理同步响应(假设HttpHelper返回已反序列化对象,需根据实际情况调整) + /// + private ERP_WMS_interactiveModelResult ProcessSyncResponse(object result, string operation, string url) + { + if (result == null) + { + _logger.Warn($"{operation}请求返回空结果 - URL: {url}"); + return null; + } + + if (result is ERP_WMS_interactiveModelResult modelResult) + { + _logger.Info($"{operation}请求处理成功 - 结果: {modelResult.result}, 消息: {modelResult.message}"); + return modelResult; + } + + _logger.Error($"{operation}请求返回意外类型 - 类型: {result.GetType().FullName}, URL: {url}"); return null; } /// - /// 出库接口 - 异步版本 + /// 处理异步响应(先校验JSON格式) /// - public async Task OutboundedAsync(string urlBase, List eRP_WMS_InteractiveModels) + private async Task ProcessAsyncResponse(string resultJson, string operation, string url) { - string url = urlBase + "/wms/mes/outbounded"; - - string contentType = "application/json"; - Dictionary headers = new Dictionary + if (string.IsNullOrEmpty(resultJson)) { - { "appid", "gN9yId!!lfwaRoi3" }, - { "timestamp", DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) }, - { "sign", GetSign.GetBy16Md5() } - }; + _logger.Warn($"{operation}请求返回空结果 - URL: {url}"); + return null; + } - string postData = JsonConvert.SerializeObject(eRP_WMS_InteractiveModels); + // 检查是否为已知异常标识 + if (resultJson == "异常:*") + { + _logger.Warn($"{operation}请求返回异常标识 - URL: {url}"); + return null; + } + // 验证JSON格式 + if (!IsValidJson(resultJson)) + { + _logger.Error($"{operation}请求返回非JSON数据 - URL: {url}, 响应内容: {resultJson}"); + return new ERP_WMS_interactiveModelResult + { + result = "fail", + message = $"{operation}响应格式错误,非JSON数据" + }; + } + + // 反序列化 try { - // 调用HttpHelper的异步方法 - string resultJson = await HttpHelper.HttpPostAsync(url, postData, contentType, 5, headers); - - // 反序列化结果 - if (!string.IsNullOrEmpty(resultJson) && resultJson != "异常:*") + var result = JsonConvert.DeserializeObject(resultJson); + if (result != null) { - return JsonConvert.DeserializeObject(resultJson); + _logger.Info($"{operation}请求处理成功 - 结果: {result.result}, 消息: {result.message}"); + return result; + } + else + { + _logger.Warn($"{operation}请求反序列化结果为空 - URL: {url}"); + return null; } } - catch (Exception ex) + catch (JsonReaderException ex) { - Console.WriteLine($"OutboundedAsync 异常: {ex.Message}"); + _logger.Error(ex, $"{operation}请求JSON解析失败 - URL: {url}, 响应内容: {resultJson}"); + return new ERP_WMS_interactiveModelResult + { + result = "fail", + message = $"{operation}响应解析错误: {ex.Message}" + }; + } + } + + /// + /// 异常处理(细化异常类型) + /// + private ERP_WMS_interactiveModelResult HandleException(Exception ex, string operation, string url, string requestData) + { + var errorMsg = $"{operation}处理异常"; + + // 区分异常类型 + if (ex is HttpRequestException httpEx) + { + _logger.Error(httpEx, $"{operation}HTTP请求失败 - URL: {url}, 请求数据: {requestData}"); + errorMsg += $": HTTP请求失败: {httpEx.Message}"; + } + else if (ex is JsonReaderException jsonEx) + { + _logger.Error(jsonEx, $"{operation}JSON解析失败 - URL: {url}, 请求数据: {requestData}"); + errorMsg += $": 数据解析失败: {jsonEx.Message}"; + } + else + { + _logger.Error(ex, $"{operation}未知异常 - URL: {url}, 请求数据: {requestData}"); + errorMsg += $": 未知错误: {ex.Message}"; } - return null; + return new ERP_WMS_interactiveModelResult + { + result = "fail", + message = errorMsg + }; } + + /// + /// 验证JSON格式是否有效 + /// + private bool IsValidJson(string json) + { + if (string.IsNullOrWhiteSpace(json)) + return false; + + json = json.Trim(); + // 基本格式校验:以{或[开头,以}或]结尾 + if ((json.StartsWith("{") && json.EndsWith("}")) || (json.StartsWith("[") && json.EndsWith("]"))) + { + try + { + // 尝试解析验证 + JsonConvert.DeserializeObject(json); + return true; + } + catch + { + return false; + } + } + + return false; + } + #endregion } -} +} \ No newline at end of file