diff --git a/ZR.Admin.WebApi/Controllers/mqtt/MqttController.cs b/ZR.Admin.WebApi/Controllers/mqtt/MqttController.cs
new file mode 100644
index 00000000..389dd860
--- /dev/null
+++ b/ZR.Admin.WebApi/Controllers/mqtt/MqttController.cs
@@ -0,0 +1,52 @@
+using Microsoft.AspNetCore.Mvc;
+using MQTTnet.Protocol;
+using ZR.Common.MqttHelper;
+using ZR.Model.MES.wms;
+using ZR.Model.MES.wms.Dto;
+using ZR.Service.mes.wms.IService;
+
+namespace ZR.Admin.WebApi.Controllers
+{
+ ///
+ /// agv 相关接口
+ ///
+
+ [Route("/mqtt")]
+ public class MqttController : BaseController
+ {
+ private readonly MqttService _mqttService;
+
+ public MqttController(MqttService mqttService)
+ {
+ _mqttService = mqttService;
+ }
+
+ ///
+ /// 1. 发布信息
+ ///
+ /// 主题
+ /// 信息
+ ///
+
+ [HttpPost("publish")]
+ public async Task PublishMessage(string topic, string payload)
+ {
+ try
+ {
+ // 发布消息到MQTT代理服务器
+ await _mqttService.PublishAsync(
+ topic,
+ payload,
+ MqttQualityOfServiceLevel.AtLeastOnce,
+ false
+ );
+
+ return Ok("消息已发布");
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, $"发布消息失败: {ex.Message}");
+ }
+ }
+ }
+}
diff --git a/ZR.Admin.WebApi/DataProtection/key-6f790ae9-1260-4b42-ac56-6835d3555186.xml b/ZR.Admin.WebApi/DataProtection/key-6f790ae9-1260-4b42-ac56-6835d3555186.xml
new file mode 100644
index 00000000..ef0573f9
--- /dev/null
+++ b/ZR.Admin.WebApi/DataProtection/key-6f790ae9-1260-4b42-ac56-6835d3555186.xml
@@ -0,0 +1,16 @@
+
+
+ 2025-05-13T00:45:18.9703791Z
+ 2025-05-13T00:45:18.9163673Z
+ 2025-08-11T00:45:18.9163673Z
+
+
+
+
+
+
+ dlysrp3IsJDOznQcS5WVSPFQ68bSJBN8cF8lnJ1+a0QzUsCf9KgzUDoQsgZ/9q/pyoKtTZJCnEoXO+m5nUaj3Q==
+
+
+
+
\ No newline at end of file
diff --git a/ZR.Admin.WebApi/Program.cs b/ZR.Admin.WebApi/Program.cs
index 5836a6b1..7c485961 100644
--- a/ZR.Admin.WebApi/Program.cs
+++ b/ZR.Admin.WebApi/Program.cs
@@ -11,6 +11,7 @@ using ZR.Admin.WebApi.Framework;
using ZR.Admin.WebApi.Hubs;
using ZR.Admin.WebApi.Middleware;
using ZR.Common.Cache;
+using ZR.Common.MqttHelper;
var builder = WebApplication.CreateBuilder(args);
@@ -21,6 +22,12 @@ builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//注入HttpContextAccessor
builder.Services.AddSingleton();
+// 注册MyMqttConfig(依赖ILogger和IConfiguration)
+builder.Services.AddSingleton();
+// 注册MqttService为单例服务,并作为后台服务运行 !!!! 这样注册就行了 ================
+builder.Services.AddSingleton();
+builder.Services.AddHostedService(sp => sp.GetRequiredService());
+/// ===============================================================================
// 跨域配置
builder.Services.AddCors(builder.Configuration);
// 显示logo
diff --git a/ZR.Admin.WebApi/appsettings.json b/ZR.Admin.WebApi/appsettings.json
index 0b55ff4e..d1643bae 100644
--- a/ZR.Admin.WebApi/appsettings.json
+++ b/ZR.Admin.WebApi/appsettings.json
@@ -1,3 +1,20 @@
{
//DOANtech123
+ "MqttConfig": {
+ "ClientId": "shgg-mes-server",
+ "Server": "192.168.23.165",
+ "Port": 1883,
+ "Username": "admin",
+ "Password": "123456",
+ "Topics": [
+ {
+ "Topic": "devices/#",
+ "QualityOfServiceLevel": "AtLeastOnce"
+ },
+ {
+ "Topic": "system/alert",
+ "QualityOfServiceLevel": "AtLeastOnce"
+ }
+ ]
+ }
}
\ No newline at end of file
diff --git a/ZR.Common/MqttHelper/MqttService.cs b/ZR.Common/MqttHelper/MqttService.cs
new file mode 100644
index 00000000..bbe25367
--- /dev/null
+++ b/ZR.Common/MqttHelper/MqttService.cs
@@ -0,0 +1,424 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Infrastructure.Attribute;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using MQTTnet;
+using MQTTnet.Client;
+using MQTTnet.Protocol;
+
+namespace ZR.Common.MqttHelper
+{
+ public class MqttService : IHostedService, IDisposable
+ {
+ private readonly ILogger _logger;
+ private readonly IConfiguration _configuration;
+ private readonly IMqttClient _mqttClient;
+ private readonly MyMqttConfig _mqttConfig; // 注入配置类
+ private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1);
+ private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
+ private Timer _reconnectTimer;
+ private bool _disposed = false;
+ private int _reconnectAttempts = 0;
+
+ // 配置常量
+ private const string MqttConfigSection = "MqttConfig";
+ private const string TopicsConfigKey = "Topics";
+ private const int DefaultReconnectDelaySeconds = 5;
+ private const int MaxReconnectAttempts = 10;
+ private const int MaxReconnectDelaySeconds = 60;
+
+ public MqttService(
+ ILogger logger,
+ IConfiguration configuration,
+ MyMqttConfig mqttConfig
+ )
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _configuration =
+ configuration ?? throw new ArgumentNullException(nameof(configuration));
+ _mqttConfig = mqttConfig ?? throw new ArgumentNullException(nameof(mqttConfig)); // 注入配置类
+ // 创建MQTT客户端
+ var factory = new MqttFactory();
+ _mqttClient = factory.CreateMqttClient();
+
+ // 注册事件处理程序
+ _mqttClient.ConnectedAsync += OnConnectedAsync;
+ _mqttClient.DisconnectedAsync += OnDisconnectedAsync;
+ _mqttClient.ApplicationMessageReceivedAsync += OnMessageReceivedAsync;
+ _logger.LogInformation($"MqttService 实例已创建,哈希值: {GetHashCode()}");
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("MQTT服务正在启动...");
+
+ try
+ {
+ await ConnectAsync(cancellationToken);
+ _logger.LogInformation("MQTT服务已成功启动");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogCritical(ex, "MQTT服务启动失败,将在后台尝试重连");
+ ScheduleReconnect(); // 即使启动失败,也应该尝试重连
+ }
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("MQTT服务正在停止...");
+
+ // 取消所有计划的重连
+ _disposeCts.Cancel();
+ _reconnectTimer?.Dispose();
+
+ await DisconnectAsync(cancellationToken);
+ _logger.LogInformation("MQTT服务已停止");
+ }
+
+ private async Task ConnectAsync(CancellationToken cancellationToken)
+ {
+ await _connectionLock.WaitAsync(cancellationToken);
+ try
+ {
+ if (_mqttClient.IsConnected)
+ {
+ _logger.LogDebug("MQTT客户端已连接,跳过连接操作");
+ return;
+ }
+ // 直接使用MyMqttConfig获取客户端选项
+ var options = _mqttConfig.BuildMqttClientOptions();
+ _logger.LogInformation($"正在连接到MQTT代理服务器 {options.ChannelOptions}...");
+ try
+ {
+ await _mqttClient.ConnectAsync(options, cancellationToken);
+ _reconnectAttempts = 0; // 重置重连计数
+ _logger.LogInformation("已成功连接到MQTT代理服务器");
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.LogWarning("MQTT连接操作被取消");
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _reconnectAttempts++;
+ _logger.LogError(
+ ex,
+ $"连接MQTT代理服务器失败 (尝试次数: {_reconnectAttempts}/{MaxReconnectAttempts})"
+ );
+
+ if (_reconnectAttempts >= MaxReconnectAttempts)
+ {
+ _logger.LogCritical("达到最大重连次数,停止尝试");
+ return;
+ }
+
+ ScheduleReconnect();
+ throw;
+ }
+ }
+ finally
+ {
+ _connectionLock.Release();
+ }
+ }
+
+ private MqttClientOptions BuildMqttClientOptions(IConfigurationSection config)
+ {
+ var builder = new MqttClientOptionsBuilder()
+ .WithClientId(config["ClientId"] ?? Guid.NewGuid().ToString())
+ .WithTcpServer(config["Server"], config.GetValue("Port", 1883))
+ .WithCleanSession();
+ return builder.Build();
+ }
+
+ private async Task DisconnectAsync(CancellationToken cancellationToken)
+ {
+ if (!_mqttClient.IsConnected)
+ {
+ _logger.LogDebug("MQTT客户端未连接,跳过断开操作");
+ return;
+ }
+
+ try
+ {
+ await _mqttClient.DisconnectAsync(cancellationToken: cancellationToken);
+ _logger.LogInformation("已成功断开与MQTT代理服务器的连接");
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.LogWarning("MQTT断开操作被取消");
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "断开MQTT连接时出错");
+ // 即使断开连接失败,也应该释放资源
+ CleanupClientResources();
+ }
+ }
+
+ private Task OnConnectedAsync(MqttClientConnectedEventArgs e)
+ {
+ _logger.LogInformation("MQTT连接已建立");
+ return SubscribeToTopicsAsync();
+ }
+
+ private async Task OnDisconnectedAsync(MqttClientDisconnectedEventArgs e)
+ {
+ _logger.LogWarning($"MQTT连接已断开 - 客户端是否之前已连接: {e.ClientWasConnected}");
+
+ if (_disposed || _disposeCts.IsCancellationRequested)
+ {
+ _logger.LogDebug("MQTT断开连接是预期行为,正在关闭服务");
+ return;
+ }
+
+ if (e.ClientWasConnected)
+ {
+ ScheduleReconnect();
+ }
+ }
+
+ private async Task OnMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs e)
+ {
+ try
+ {
+ var payload = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
+ _logger.LogInformation(
+ $"收到MQTT消息 - 主题: {e.ApplicationMessage.Topic}, QoS: {e.ApplicationMessage.QualityOfServiceLevel}"
+ );
+
+ // 消息处理委托给专用的处理器
+ await ProcessMessageAsync(e.ApplicationMessage.Topic, payload);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "处理MQTT消息时出错");
+ }
+ }
+
+ private async Task ProcessMessageAsync(string topic, string payload)
+ {
+ try
+ {
+ // 这里可以根据主题路由消息到不同的处理程序
+ switch (topic)
+ {
+ case string t when t.StartsWith("devices/"):
+ await HandleDeviceMessage(topic, payload);
+ break;
+ case "system/alert":
+ await HandleSystemAlert(payload);
+ break;
+ default:
+ _logger.LogDebug($"未处理的MQTT主题: {topic}");
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"处理主题 '{topic}' 的消息时出错");
+ }
+ }
+
+ private async Task SubscribeToTopicsAsync()
+ {
+ if (!_mqttClient.IsConnected)
+ {
+ _logger.LogWarning("无法订阅主题:MQTT客户端未连接");
+ return;
+ }
+
+ try
+ {
+ // 获取配置类生成的订阅选项
+ 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}"
+ );
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "订阅MQTT主题时出错");
+ ScheduleReconnect();
+ }
+ }
+
+ private void ScheduleReconnect()
+ {
+ // 实现指数退避算法
+ var delaySeconds = Math.Min(
+ DefaultReconnectDelaySeconds * (int)Math.Pow(2, Math.Min(_reconnectAttempts, 5)),
+ MaxReconnectDelaySeconds
+ );
+
+ _logger.LogInformation($"计划在 {delaySeconds} 秒后尝试重新连接MQTT代理服务器");
+
+ // 使用Timer替代Task.Run,更好地控制资源
+ _reconnectTimer?.Dispose();
+ _reconnectTimer = new Timer(
+ async _ =>
+ {
+ if (_disposed || _disposeCts.IsCancellationRequested)
+ {
+ _logger.LogDebug("跳过重连:服务正在关闭");
+ return;
+ }
+
+ try
+ {
+ await ConnectAsync(_disposeCts.Token);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "计划的MQTT重连失败");
+ }
+ },
+ null,
+ TimeSpan.FromSeconds(delaySeconds),
+ Timeout.InfiniteTimeSpan
+ );
+ }
+
+ private Task HandleDeviceMessage(string topic, string payload)
+ {
+ _logger.LogInformation($"处理设备消息: {topic} - {payload}");
+ // 这里添加设备消息处理逻辑
+ return Task.CompletedTask;
+ }
+
+ private Task HandleSystemAlert(string payload)
+ {
+ _logger.LogWarning($"系统警报: {payload}");
+ // 这里添加系统警报处理逻辑
+ return Task.CompletedTask;
+ }
+
+ public async Task PublishAsync(
+ string topic,
+ string payload,
+ MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtLeastOnce,
+ bool retain = false
+ )
+ {
+ if (string.IsNullOrEmpty(topic))
+ {
+ throw new ArgumentNullException(nameof(topic), "MQTT主题不能为空");
+ }
+
+ if (payload == null)
+ {
+ throw new ArgumentNullException(nameof(payload), "MQTT消息内容不能为空");
+ }
+
+ await _connectionLock.WaitAsync(_disposeCts.Token);
+ try
+ {
+ if (!_mqttClient.IsConnected)
+ {
+ _logger.LogWarning("发布消息前需要连接MQTT代理服务器");
+ //await ConnectAsync(_disposeCts.Token);
+ throw new Exception("发布消息前需要连接MQTT代理服务器");
+ }
+
+ var message = new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .WithQualityOfServiceLevel(qos)
+ .WithRetainFlag(retain)
+ .Build();
+
+ _logger.LogDebug($"准备发布MQTT消息 - 主题: {topic}, QoS: {qos}, 保留: {retain}");
+
+ try
+ {
+ var result = await _mqttClient.PublishAsync(message, _disposeCts.Token);
+ _logger.LogInformation($"已发布MQTT消息 - 主题: {topic}, 结果: {result.ReasonCode}");
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.LogWarning("MQTT发布操作被取消");
+ throw;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"发布MQTT消息失败 - 主题: {topic}");
+ throw;
+ }
+ finally
+ {
+ _connectionLock.Release();
+ }
+ }
+
+ private void CleanupClientResources()
+ {
+ try
+ {
+ _mqttClient.ApplicationMessageReceivedAsync -= OnMessageReceivedAsync;
+ _mqttClient.ConnectedAsync -= OnConnectedAsync;
+ _mqttClient.DisconnectedAsync -= OnDisconnectedAsync;
+
+ if (_mqttClient.IsConnected)
+ {
+ _mqttClient.DisconnectAsync().GetAwaiter().GetResult();
+ }
+
+ _mqttClient.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "清理MQTT客户端资源时出错");
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _disposeCts.Cancel();
+ _reconnectTimer?.Dispose();
+ _connectionLock.Dispose();
+ CleanupClientResources();
+ _disposeCts.Dispose();
+ }
+
+ _disposed = true;
+ _logger.LogDebug("MQTT服务已释放所有资源");
+ }
+ }
+ }
+}
diff --git a/ZR.Common/MqttHelper/MyMqttConfig.cs b/ZR.Common/MqttHelper/MyMqttConfig.cs
new file mode 100644
index 00000000..4d85b4e3
--- /dev/null
+++ b/ZR.Common/MqttHelper/MyMqttConfig.cs
@@ -0,0 +1,97 @@
+using Infrastructure.Attribute;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using MQTTnet;
+using MQTTnet.Client;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
+using MQTTnet.Server;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static Org.BouncyCastle.Math.EC.ECCurve;
+
+namespace ZR.Common.MqttHelper
+{
+ [AppService(ServiceLifetime = LifeTime.Singleton)]
+ public class MyMqttConfig
+ {
+ // 配置常量
+ private const string MqttConfigSection = "MqttConfig";
+ private const string TopicsConfigKey = "Topics";
+ private const int DefaultReconnectDelaySeconds = 5;
+ private const int MaxReconnectAttempts = 10;
+ private const int MaxReconnectDelaySeconds = 60;
+
+ private readonly ILogger _logger;
+ private readonly IConfiguration _configuration;
+
+ public MyMqttConfig(ILogger logger, IConfiguration configuration)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _configuration = configuration;
+ }
+
+ public MqttClientOptions BuildMqttClientOptions()
+ {
+ var mqttConfig = _configuration.GetSection(MqttConfigSection);
+ var builder = new MqttClientOptionsBuilder()
+ .WithClientId(mqttConfig["ClientId"] ?? Guid.NewGuid().ToString())
+ .WithTcpServer(mqttConfig["Server"], mqttConfig.GetValue("Port", 1883))
+ .WithCleanSession();
+ return builder.Build();
+ }
+
+ public MqttClientSubscribeOptions GetSubscribeToTopicsAsync()
+ {
+ var topicsSection = _configuration.GetSection($"{MqttConfigSection}:{TopicsConfigKey}");
+ var topicConfigs = topicsSection.Get>() ?? new List();
+
+ if (!topicConfigs.Any())
+ {
+ _logger.LogInformation("没有配置要订阅的MQTT主题");
+ return null; // 或返回空配置,根据业务需求决定
+ }
+
+ var subscribeOptionsBuilder = new MqttClientSubscribeOptionsBuilder();
+
+ foreach (var config in topicConfigs)
+ {
+ var topicFilter = config.ToMqttTopicFilter();
+ subscribeOptionsBuilder.WithTopicFilter(topicFilter); // 直接添加单个主题过滤器
+ }
+
+ return subscribeOptionsBuilder.Build();
+ }
+
+
+
+ // 主题订阅配置类
+ private class TopicSubscriptionConfig
+ {
+ public string Topic { get; set; }
+ public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtLeastOnce;
+ public bool NoLocal { get; set; } = false;
+ public bool RetainAsPublished { get; set; } = false;
+ public MqttRetainHandling RetainHandling { get; set; } = MqttRetainHandling.SendAtSubscribe;
+
+ public MqttTopicFilter ToMqttTopicFilter()
+ {
+ return new MqttTopicFilterBuilder()
+ .WithTopic(Topic)
+ .WithQualityOfServiceLevel(QualityOfServiceLevel)
+ .WithNoLocal(NoLocal)
+ .WithRetainAsPublished(RetainAsPublished)
+ .WithRetainHandling(RetainHandling)
+ .Build();
+ }
+
+ public override string ToString()
+ {
+ return $"{Topic}@QoS{QualityOfServiceLevel}";
+ }
+ }
+ }
+}
diff --git a/ZR.Common/ZR.Common.csproj b/ZR.Common/ZR.Common.csproj
index b890470b..2d152dd6 100644
--- a/ZR.Common/ZR.Common.csproj
+++ b/ZR.Common/ZR.Common.csproj
@@ -10,7 +10,8 @@
-
+
+
diff --git a/ZR.Model/MES/qc/DTO/backend/QcBackEndPrintMqttEventDto.cs b/ZR.Model/MES/qc/DTO/backend/QcBackEndPrintMqttEventDto.cs
new file mode 100644
index 00000000..6a453019
--- /dev/null
+++ b/ZR.Model/MES/qc/DTO/backend/QcBackEndPrintMqttEventDto.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace ZR.Model.Dto
+{
+ ///
+ /// 后道外箱标签打印发送消息模板
+ ///
+ public class QcBackEndPrintMqttEventDto
+ {
+ public string Path { get; set; }
+
+ public string SiteNo { get; set; }
+
+ public string Name { get; set; }
+
+ public string PartNumber { get; set; }
+
+ public string WorkOrder { get; set; }
+
+ public string Team { get; set; } = "A";
+
+ public int Sort { get; set; } = 1;
+
+ public string BatchCode { get; set; }
+
+ public int PackageNum { get; set; } = 24;
+
+ public int LabelType { get; set; } = 1;
+
+ public string CreatedTime { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+ }
+}
\ No newline at end of file
diff --git a/ZR.Service/ZR.Service.csproj b/ZR.Service/ZR.Service.csproj
index 582a50fb..0da804f5 100644
--- a/ZR.Service/ZR.Service.csproj
+++ b/ZR.Service/ZR.Service.csproj
@@ -21,4 +21,8 @@
+
+
+
+
diff --git a/ZR.Service/mes/qc/backend/QcBackEndService.cs b/ZR.Service/mes/qc/backend/QcBackEndService.cs
index f5d6a52c..6fc7ca69 100644
--- a/ZR.Service/mes/qc/backend/QcBackEndService.cs
+++ b/ZR.Service/mes/qc/backend/QcBackEndService.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
+using System.Threading.Tasks;
using System.Transactions;
using Aliyun.OSS;
using AutoMapper;
@@ -10,7 +11,10 @@ using Infrastructure.Attribute;
using Infrastructure.Extensions;
using JinianNet.JNTemplate;
using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Protocol;
using SqlSugar;
+using ZR.Common.MqttHelper;
using ZR.Model;
using ZR.Model.Business;
using ZR.Model.Dto;
@@ -27,6 +31,15 @@ namespace ZR.Service.Business
[AppService(ServiceType = typeof(IQcBackEndService), ServiceLifetime = LifeTime.Transient)]
public class QcBackEndService : BaseService, IQcBackEndService
{
+ private readonly MqttService _mqttService; // 注入MqttService
+ private readonly ILogger _logger;
+
+ public QcBackEndService(MqttService mqttService, ILogger logger)
+ {
+ _mqttService = mqttService;
+ _logger = logger;
+ }
+
public QcBackEndLabelAnalysisDto AnalyzeLabelToDto(string label, int type)
{
QcBackEndLabelAnalysisDto labelAnalysisDto =
@@ -158,21 +171,16 @@ namespace ZR.Service.Business
public List GetDefectInitOptions()
{
List defectList = new();
- var predicate = Expressionable.Create().And(it => it.Status == "1");
+ var predicate = Expressionable
+ .Create()
+ .And(it => it.Status == "1");
/*List groupList = Context
.Queryable()
.Where(predicate.ToExpression())
.GroupBy(it => it.Group)
.Select(it => it.Group)
.ToList();*/
- List groupList = new()
- {
- "油漆",
- "设备",
- "毛坯",
- "程序",
- "班组操作"
- };
+ List groupList = new() { "油漆", "设备", "毛坯", "程序", "班组操作" };
foreach (string group in groupList)
{
QcBackEndAlterationDefectDto defectDto = new();
@@ -202,20 +210,13 @@ namespace ZR.Service.Business
.Create()
.And(it => it.Type == "打磨")
.And(it => it.Status == "1");
- /* List groupList = Context
- .Queryable()
- .Where(predicate.ToExpression())
- .GroupBy(it => it.Group)
- .Select(it => it.Group)
- .ToList();*/
- List groupList = new()
- {
- "油漆",
- "设备",
- "毛坯",
- "程序",
- "班组操作"
- };
+ /* List groupList = Context
+ .Queryable()
+ .Where(predicate.ToExpression())
+ .GroupBy(it => it.Group)
+ .Select(it => it.Group)
+ .ToList();*/
+ List groupList = new() { "油漆", "设备", "毛坯", "程序", "班组操作" };
foreach (string group in groupList)
{
QcBackEndAlterationDefectDto defectDto = new();
@@ -302,7 +303,9 @@ namespace ZR.Service.Business
data.SerialNumber = workorderInfo.SerialNumber;
data.StartTime = nowTime;
QcBackEndServiceWorkorder newModel = GetNewWorkOrderInfo(data);
- QcBackEndServiceWorkorder result = Context.Insertable(newModel).ExecuteReturnEntity();
+ QcBackEndServiceWorkorder result = Context
+ .Insertable(newModel)
+ .ExecuteReturnEntity();
if (result == null)
{
Context.Ado.RollbackTran();
@@ -409,7 +412,9 @@ namespace ZR.Service.Business
return result;
}
- public static QcBackEndServiceWorkorder GetNewWorkOrderInfo(QcBackEndWorkorderDetailDto data)
+ public static QcBackEndServiceWorkorder GetNewWorkOrderInfo(
+ QcBackEndWorkorderDetailDto data
+ )
{
// 新工单
@@ -453,7 +458,7 @@ namespace ZR.Service.Business
{
try
{
- if(string.IsNullOrEmpty(data.DefectCode))
+ if (string.IsNullOrEmpty(data.DefectCode))
{
throw new Exception("缺陷项传入为空!");
}
@@ -643,10 +648,11 @@ namespace ZR.Service.Business
bool hasAny = Context
.Queryable()
.Where(it => it.Label == data.Label)
+ .Where(it => it.LabelType == 2)
.Any();
if (hasAny)
{
- return "重复扫码!";
+ return "内标签重复扫码!";
}
// 标签录入
int sort = 0;
@@ -685,41 +691,124 @@ namespace ZR.Service.Business
return "标签录入系统失败!";
}
//TODO 触发箱标签判定
- if(sort > 28 && (sort + 1) % 28 == 0)
+ CheckAndPrintPackageLabel(newLabelScran);
+
+ return "ok";
+ }
+
+ ///
+ /// 判断是否需要自动出满箱标签
+ ///
+ ///
+ public void CheckAndPrintPackageLabel(QcBackEndRecordLabelScan newLabelScran)
+ {
+ DateTime nowTime = DateTime.Now;
+ // 找到最大箱容量与模板
+ QcBackEndServiceWorkorder workorder = Context
+ .Queryable()
+ .Where(it => it.WorkOrder == newLabelScran.WorkOrder)
+ .First();
+ QcBackendBaseOutpackage packageLabelConfig = Context
+ .Queryable()
+ .Where(it => workorder.Description.Contains(it.CheckStr))
+ .First();
+ if (workorder == null)
{
- /* int packageSort = 0;
+ throw new Exception("工单异常");
+ }
+ if (packageLabelConfig == null)
+ {
+ throw new Exception("该标签打印参数未配置");
+ }
+ int checkSort = newLabelScran.LabelSort ?? 0;
+
+ int maxPackage = packageLabelConfig.PackageNum ?? 0;
+ if (checkSort >= maxPackage && checkSort % maxPackage == 0)
+ {
+ int packageSort = 0;
QcBackEndRecordLabelScan packagelabelScan = Context
.Queryable()
- .Where(it => it.WorkOrder == data.WorkOrder)
+ .Where(it => it.WorkOrder == newLabelScran.WorkOrder)
.Where(it => it.LabelType == 1)
.OrderByDescending(it => it.LabelSort)
.First();
- if (labelScan != null)
+ if (packagelabelScan != null)
{
packageSort = packagelabelScan.LabelSort ?? 0;
}
QcBackEndRecordLabelScan newPackagePrintLabel =
- new()
+ new()
+ {
+ Id = SnowFlakeSingle.Instance.NextId().ToString(),
+ WorkOrder = newLabelScran.WorkOrder,
+ PartNumber = newLabelScran.PartNumber,
+ Team = newLabelScran.Team,
+ SiteNo = newLabelScran.SiteNo,
+ ComNo = newLabelScran.ComNo,
+ Label =
+ $"Code=BN{newLabelScran.WorkOrder}_{newLabelScran.Team}{packageSort + 1}^ItemNumber={newLabelScran.PartNumber}^Order={newLabelScran.WorkOrder}^Qty={maxPackage}^Type=packageLabel",
+ LabelType = 1,
+ LabelSort = packageSort + 1,
+ ScanTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}",
+ Type = "1",
+ Status = "1",
+ Remark = "自动出满箱标签",
+ CreatedBy = newLabelScran.CreatedBy,
+ CreatedTime = newLabelScran.CreatedTime,
+ };
+ int res = Context.Insertable(newPackagePrintLabel).ExecuteCommand();
+ if (res > 0)
{
- Id = SnowFlakeSingle.Instance.NextId().ToString(),
- WorkOrder = data.WorkOrder,
- PartNumber = data.PartNumber,
- Team = data.Team,
- SiteNo = data.SiteNo,
- ComNo = data.ComNo,
- Label = data.Label,
- LabelType = 1,
- LabelSort = packageSort + 1,
- ScanTime = $"{nowTime:yyyy-MM-dd HH:mm:ss}",
- Type = "1",
- Status = "1",
- Remark = "自动出满箱标签",
- CreatedBy = data.CreatedBy,
- CreatedTime = data.CreatedTime,
- };
- int res2 = Context.Insertable(newPackagePrintLabel).ExecuteCommand();*/
+ SendPrintPackageLabelAsync(newLabelScran, packageLabelConfig.FileUrl).Wait();
+ }
+ }
+ }
+
+ ///
+ /// 发送打印后道外箱标签的mqtt信息
+ ///
+ public async Task SendPrintPackageLabelAsync(
+ QcBackEndRecordLabelScan newLabelScran,
+ string path
+ )
+ {
+ try
+ {
+ // 构造主题和消息内容
+ string topic = $"shgg_mes/backEnd/print/{newLabelScran.SiteNo}";
+
+ QcBackEndPrintMqttEventDto mqttEventDto =
+ new()
+ {
+ Path = path,
+ SiteNo = newLabelScran.SiteNo,
+ Name = newLabelScran.PartNumber,
+ WorkOrder = newLabelScran.WorkOrder,
+ Team = newLabelScran.Team,
+ Sort = (newLabelScran.LabelSort + 1) ?? 1,
+ BatchCode = DateTime.Now.ToString("yyyyMMdd"),
+ PackageNum = 24,
+ LabelType = newLabelScran.LabelType ?? 1,
+ CreatedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+ };
+ var payload = JsonSerializer.Serialize(mqttEventDto);
+
+ // 调用MqttService的发布方法(支持异步调用)
+ await _mqttService.PublishAsync(
+ topic,
+ payload,
+ MqttQualityOfServiceLevel.ExactlyOnce,
+ // 可选:设置消息保留
+ retain: false
+ );
+
+ _logger.LogInformation($"发送后道外箱标签打印成功:{topic}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"发送后道外箱标签打印失败:{ex.Message}");
+ throw; // 或根据业务需求处理异常
}
- return "ok";
}
public string EndBackEndWorkOrderAndCreateStatistics(string workorder)
@@ -847,7 +936,9 @@ namespace ZR.Service.Business
return $"{qualifiedRate:F1}%";
}
- public QcBackEndServiceWorkorder GenerateVirtualLabel(QcBackEndWorkorderDetailDto workorderDetail)
+ public QcBackEndServiceWorkorder GenerateVirtualLabel(
+ QcBackEndWorkorderDetailDto workorderDetail
+ )
{
try
{
@@ -857,7 +948,10 @@ namespace ZR.Service.Business
int qualifiedNumber = workorderDetail.QualifiedNumber ?? -1;
if (qualifiedNumber < 0)
{
- throw new ArgumentException("传入合格数异常!", nameof(workorderDetail.QualifiedNumber));
+ throw new ArgumentException(
+ "传入合格数异常!",
+ nameof(workorderDetail.QualifiedNumber)
+ );
}
int labelCount = GetLabelCountForWorkOrder(workorderDetail.WorkOrder);
@@ -883,37 +977,46 @@ namespace ZR.Service.Business
private int GetLabelCountForWorkOrder(string workOrder)
{
- return Context.Queryable()
- .Where(it => it.WorkOrder == workOrder && it.LabelType == 2)
- .Count();
+ return Context
+ .Queryable()
+ .Where(it => it.WorkOrder == workOrder && it.LabelType == 2)
+ .Count();
}
- private void GenerateVirtualLabels(QcBackEndWorkorderDetailDto workOrderDetail, int countToGenerate)
+ private void GenerateVirtualLabels(
+ QcBackEndWorkorderDetailDto workOrderDetail,
+ int countToGenerate
+ )
{
List virtualLabels = new List();
int nextLabelNumber = GetNextLabelNumber(workOrderDetail.WorkOrder);
for (int i = 0; i < countToGenerate; i++)
{
- string uniqueLabel = GenerateUniqueSequentialLabel(workOrderDetail.WorkOrder, nextLabelNumber++);
- virtualLabels.Add(new QcBackEndRecordLabelScan
- {
- Id = SnowFlakeSingle.Instance.NextId().ToString(),
- WorkOrder = workOrderDetail.WorkOrder,
- PartNumber = workOrderDetail.PartNumber,
- Team = workOrderDetail.Team,
- SiteNo = workOrderDetail.SiteNo,
- ComNo = workOrderDetail.ComNo,
- ScanTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"),
- Type = "2",
- Status = "1",
- Remark = "虚拟标签",
- CreatedTime = DateTime.UtcNow,
- CreatedBy = "系统",
- LabelType = 2,
- LabelSort = nextLabelNumber,
- Label = uniqueLabel
- });
+ string uniqueLabel = GenerateUniqueSequentialLabel(
+ workOrderDetail.WorkOrder,
+ nextLabelNumber++
+ );
+ virtualLabels.Add(
+ new QcBackEndRecordLabelScan
+ {
+ Id = SnowFlakeSingle.Instance.NextId().ToString(),
+ WorkOrder = workOrderDetail.WorkOrder,
+ PartNumber = workOrderDetail.PartNumber,
+ Team = workOrderDetail.Team,
+ SiteNo = workOrderDetail.SiteNo,
+ ComNo = workOrderDetail.ComNo,
+ ScanTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"),
+ Type = "2",
+ Status = "1",
+ Remark = "虚拟标签",
+ CreatedTime = DateTime.UtcNow,
+ CreatedBy = "系统",
+ LabelType = 2,
+ LabelSort = nextLabelNumber,
+ Label = uniqueLabel
+ }
+ );
}
Context.Insertable(virtualLabels).ExecuteCommand();
@@ -921,20 +1024,22 @@ namespace ZR.Service.Business
private void DeleteExcessLabels(string workOrder, int countToDelete)
{
- var labelsToDelete = Context.Queryable()
- .Where(it => it.WorkOrder == workOrder && it.LabelType == 2)
- .OrderByDescending(it => it.LabelSort)
- .Take(countToDelete)
- .ToList();
+ var labelsToDelete = Context
+ .Queryable()
+ .Where(it => it.WorkOrder == workOrder && it.LabelType == 2)
+ .OrderByDescending(it => it.LabelSort)
+ .Take(countToDelete)
+ .ToList();
Context.Deleteable(labelsToDelete).ExecuteCommand();
}
private int GetNextLabelNumber(string workOrder)
{
- return Context.Queryable()
- .Where(it => it.WorkOrder == workOrder && it.LabelType == 2)
- .Max(it => it.LabelSort ?? 0);
+ return Context
+ .Queryable()
+ .Where(it => it.WorkOrder == workOrder && it.LabelType == 2)
+ .Max(it => it.LabelSort ?? 0);
}
private string GenerateUniqueSequentialLabel(string workOrder, int number)
@@ -952,14 +1057,14 @@ namespace ZR.Service.Business
private bool IsLabelExists(string workOrder, string label)
{
- return Context.Queryable()
- .Any(it => it.WorkOrder == workOrder && it.LabelType == 2 && it.Label == label);
+ return Context
+ .Queryable()
+ .Any(it => it.WorkOrder == workOrder && it.LabelType == 2 && it.Label == label);
}
private string GenerateUniqueId()
{
return Guid.NewGuid().ToString("N").Substring(0, 10); // Generate a 10-character unique ID
}
-
}
}