using System.Net.Sockets;
|
using WIDESEAWCS_Common.HttpEnum;
|
using WIDESEAWCS_Common.TaskEnum;
|
using WIDESEAWCS_DTO.TaskInfo;
|
using WIDESEAWCS_ITaskInfoService;
|
using WIDESEAWCS_Model.Models;
|
using WIDESEAWCS_Tasks.Workflow.Abstractions;
|
|
namespace WIDESEAWCS_Tasks.Workflow
|
{
|
/// <summary>
|
/// 前缀命令处理器
|
/// </summary>
|
/// <remarks>
|
/// 迁移原 RobotMessageHandler 的 pickfinished/putfinished 分支。
|
///
|
/// 前缀命令是指以特定前缀开头的命令,后面跟随逗号分隔的参数。
|
/// 格式:{前缀},{参数1},{参数2},...
|
///
|
/// 当前支持的前缀命令:
|
/// - pickfinished: 取货完成,后面跟随完成的位置编号列表
|
/// - putfinished: 放货完成,后面跟随完成的位置编号列表
|
///
|
/// 这些命令通常包含取货或放货的位置信息,需要解析并更新状态。
|
/// </remarks>
|
public class RobotPrefixCommandHandler : IRobotPrefixCommandHandler
|
{
|
/// <summary>
|
/// 机器人任务服务
|
/// </summary>
|
/// <remarks>
|
/// 用于查询和更新任务记录。
|
/// </remarks>
|
private readonly IRobotTaskService _robotTaskService;
|
|
/// <summary>
|
/// 任务处理器
|
/// </summary>
|
/// <remarks>
|
/// 用于处理取货/放货完成时的业务逻辑,如调用拆盘/组盘 API。
|
/// </remarks>
|
private readonly RobotTaskProcessor _taskProcessor;
|
|
/// <summary>
|
/// 状态管理器
|
/// </summary>
|
/// <remarks>
|
/// 用于安全更新机器人的状态。
|
/// </remarks>
|
private readonly RobotStateManager _stateManager;
|
|
/// <summary>
|
/// Socket 网关
|
/// </summary>
|
/// <remarks>
|
/// 用于向客户端发送响应消息。
|
/// </remarks>
|
private readonly ISocketClientGateway _socketClientGateway;
|
|
/// <summary>
|
/// 构造函数
|
/// </summary>
|
/// <param name="robotTaskService">任务服务</param>
|
/// <param name="taskProcessor">任务处理器</param>
|
/// <param name="stateManager">状态管理器</param>
|
/// <param name="socketClientGateway">Socket 网关</param>
|
public RobotPrefixCommandHandler(
|
IRobotTaskService robotTaskService,
|
RobotTaskProcessor taskProcessor,
|
RobotStateManager stateManager,
|
ISocketClientGateway socketClientGateway)
|
{
|
_robotTaskService = robotTaskService;
|
_taskProcessor = taskProcessor;
|
_stateManager = stateManager;
|
_socketClientGateway = socketClientGateway;
|
}
|
|
/// <summary>
|
/// 检查消息是否为前缀命令
|
/// </summary>
|
/// <remarks>
|
/// 前缀命令必须以 "pickfinished" 或 "putfinished" 开头(不区分大小写)。
|
/// </remarks>
|
/// <param name="message">消息内容(小写形式)</param>
|
/// <returns>如果是指缀命令返回 true</returns>
|
public bool IsPrefixCommand(string message)
|
{
|
// 检查消息是否以 pickfinished 或 putfinished 开头
|
return message.StartsWith("pickfinished") || message.StartsWith("putfinished");
|
}
|
|
/// <summary>
|
/// 处理前缀命令
|
/// </summary>
|
/// <remarks>
|
/// 处理流程:
|
/// 1. 解析消息,提取位置参数
|
/// 2. 查询当前任务
|
/// 3. 根据命令类型调用相应的处理方法
|
/// 4. 回写原消息到客户端
|
///
|
/// 消息格式:{命令前缀},{位置1},{位置2},...
|
/// 示例:pickfinished,1,2,3 表示取货完成,位置 1、2、3 的货物已取走
|
/// </remarks>
|
/// <param name="message">原始消息内容</param>
|
/// <param name="state">机器人当前状态</param>
|
/// <param name="client">TCP 客户端连接,用于发送响应</param>
|
public async Task HandleAsync(string message, RobotSocketState state, TcpClient client)
|
{
|
try
|
{
|
// 按逗号分隔消息,提取命令和参数
|
// 例如:pickfinished,1,2,3 -> ["pickfinished", "1", "2", "3"]
|
var parts = message.Split(',');
|
|
// 检查消息格式是否有效:至少要有命令前缀,且状态中有当前任务
|
if (parts.Length < 1 || state.CurrentTask == null)
|
{
|
return;
|
}
|
|
// 提取命令前缀并转换为小写
|
var cmd = parts[0].ToLowerInvariant();
|
|
// 解析位置参数(跳过命令前缀,处理后面的数字)
|
// 过滤掉无法解析为数字或值为 0 的位置
|
int[] positions = parts.Skip(1)
|
.Select(p => int.TryParse(p, out int value) ? value : (int?)null) // 尝试解析为整数
|
.Where(v => v.HasValue && v.Value != 0) // 过滤掉 null 和 0
|
.Select(v => v!.Value) // 提取值(已知非 null)
|
.ToArray();
|
|
// 从数据库重新查询当前任务(确保获取最新状态)
|
var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId);
|
|
// 根据命令前缀分发处理
|
if (cmd.StartsWith("pickfinished"))
|
{
|
// 处理取货完成
|
await HandlePickFinishedAsync(state, positions, task);
|
}
|
else if (cmd.StartsWith("putfinished"))
|
{
|
// 处理放货完成
|
await HandlePutFinishedAsync(state, positions, task);
|
}
|
|
// 回写原消息到客户端(保持原有行为)
|
await _socketClientGateway.SendMessageAsync(client, message);
|
}
|
catch (Exception ex)
|
{
|
// 捕获并记录异常,防止异常向上传播导致消息处理中断
|
Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 处理取货完成(pickfinished)命令
|
/// </summary>
|
/// <remarks>
|
/// 处理逻辑:
|
/// 1. 如果是拆盘任务,构建库存 DTO 并调用拆盘 API
|
/// 2. 更新当前动作为"取货完成"
|
/// 3. 记录取货完成的位置
|
/// 4. 更新任务状态为"机器人取货完成"
|
/// 5. 安全更新状态到 Redis
|
/// </remarks>
|
/// <param name="state">机器人当前状态</param>
|
/// <param name="positions">取货完成的位置编号数组</param>
|
/// <param name="task">机器人任务记录</param>
|
private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
|
{
|
// 如果是拆盘任务
|
if (state.IsSplitPallet)
|
{
|
// 构建库存 DTO,包含位置信息和托盘条码
|
var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
|
|
// 记录取货完成的位置
|
state.LastPickPositions = positions;
|
|
// 调用拆盘 API
|
var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
|
|
// 如果 API 调用成功
|
if (result.Data.Status && result.IsSuccess)
|
{
|
// 更新当前动作为"取货完成"
|
state.CurrentAction = "PickFinished";
|
}
|
}
|
else
|
{
|
// 非拆盘任务,直接更新动作
|
state.CurrentAction = "PickFinished";
|
}
|
|
// 记录取货完成的位置(无论是否拆盘都记录)
|
state.LastPickPositions = positions;
|
|
// 如果任务存在
|
if (task != null)
|
{
|
// 更新任务状态为"机器人取货完成"
|
task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode();
|
|
// 安全更新状态到 Redis,确保更新成功后再更新数据库
|
if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
|
{
|
await _robotTaskService.Repository.UpdateDataAsync(task);
|
}
|
}
|
}
|
|
/// <summary>
|
/// 处理放货完成(putfinished)命令
|
/// </summary>
|
/// <remarks>
|
/// 处理逻辑:
|
/// 1. 如果是组盘任务,构建库存 DTO 并调用组盘/换盘 API
|
/// 2. 如果组盘成功,增加任务计数
|
/// 3. 更新当前动作为"放货完成"
|
/// 4. 更新任务状态为"机器人放货完成"
|
/// 5. 安全更新状态到 Redis
|
/// </remarks>
|
/// <param name="state">机器人当前状态</param>
|
/// <param name="positions">放货完成的位置编号数组</param>
|
/// <param name="task">机器人任务记录</param>
|
private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
|
{
|
// 假设放货成功(如果后续 API 调用失败也不回退计数)
|
bool putSuccess = true;
|
|
// 如果是组盘任务(包含换盘)
|
if (state.IsGroupPallet)
|
{
|
// 记录放货完成的位置
|
state.LastPutPositions = positions;
|
|
// 构建库存 DTO
|
var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
|
|
// 根据任务类型决定调用哪个 API
|
// 换盘任务调用 ChangePalletAsync,组盘任务调用 GroupPalletAsync
|
var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
|
? nameof(ConfigKey.ChangePalletAsync)
|
: nameof(ConfigKey.GroupPalletAsync);
|
|
// 调用组盘/换盘 API
|
var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO);
|
|
// 检查 API 返回状态
|
putSuccess = result.Data.Status && result.IsSuccess;
|
}
|
|
// 如果放货成功
|
if (putSuccess)
|
{
|
// 更新当前动作为"放货完成"
|
state.CurrentAction = "PutFinished";
|
|
// 增加任务计数(累加本次完成的数量)
|
state.RobotTaskTotalNum += positions.Length;
|
|
// 如果任务存在,同步更新任务的计数
|
if (task != null)
|
{
|
task.RobotTaskTotalNum += positions.Length;
|
}
|
}
|
|
// 如果任务存在
|
if (task != null)
|
{
|
// 更新任务状态为"机器人放货完成"
|
task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode();
|
|
// 安全更新状态到 Redis
|
if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
|
{
|
await _robotTaskService.Repository.UpdateDataAsync(task);
|
}
|
}
|
}
|
}
|
}
|