SyncMapPointsCommandHandler.cs 8.46 KB
using MassTransit;
using MassTransit.Mediator;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Rcs.Application.Common;
using Rcs.Application.MessageBus.Commands;
using Rcs.Application.Shared;
using Rcs.Cyaninetech.Services;
using Rcs.Domain.Entities;
using Rcs.Domain.Extensions;
using Rcs.Domain.Repositories;
using Rcs.Domain.Settings;

namespace Rcs.Infrastructure.MessageBus.Handlers.Commands;

/// <summary>
/// 同步地图资源命令处理器
/// @author zzy
/// </summary>
public class SyncMapPointsCommandHandler : IConsumer<SyncMapPointsCommand>
{
    private readonly ILogger<SyncMapPointsCommandHandler> _logger;
    private readonly IMapRepository _mapRepository;
    private readonly IMapNodeRepository _mapNodeRepository;
    private readonly IStorageAreaRepository _storageAreaRepository;
    private readonly IStorageLocationRepository _storageLocationRepository;
    private readonly ILanYinService _lanYinService;

    public SyncMapPointsCommandHandler(
        ILogger<SyncMapPointsCommandHandler> logger,
        IMapRepository mapRepository,
        IMapNodeRepository mapNodeRepository,
        IStorageAreaRepository storageAreaRepository,
        IStorageLocationRepository storageLocationRepository,
        ILanYinService lanYinService)
    {
        _logger = logger;
        _mapRepository = mapRepository;
        _mapNodeRepository = mapNodeRepository;
        _storageAreaRepository = storageAreaRepository;
        _storageLocationRepository = storageLocationRepository;
        _lanYinService = lanYinService;
    }

    public async Task Consume(ConsumeContext<SyncMapPointsCommand> context)
    {
        var command = context.Message;

        try
        {
            var map = await _mapRepository.GetByIdAsync(command.MapId, context.CancellationToken);
            if (map == null)
            {
                await context.RespondAsync(ApiResponse.Failed($"未找到ID为 {command.MapId} 的地图"));
                return;
            }

            if (string.IsNullOrWhiteSpace(map.PointsUrl))
                throw new BusinessException("请先维护节点资源URL");

            // 获取地图资源信息
            var locations = await _lanYinService.GetLocationsAsync(map.PointsUrl);
            var remoteNodeCodes = locations.Select(l => l.id).ToHashSet();

            // 获取当前地图所有节点,用于后续删除判断
            var existingNodes = await _mapNodeRepository.GetByMapIdAsync(command.MapId, context.CancellationToken);

            // 先同步库区(以location.area为库区编码)
            var areaDict = new Dictionary<string, StorageArea>();
            var areaCodes = locations.Select(l => l.area).Where(a => !string.IsNullOrWhiteSpace(a)).Distinct();
            foreach (var areaCode in areaCodes)
            {
                var storageArea = await _storageAreaRepository.GetByAreaCodeAsync(areaCode, context.CancellationToken);
                if (storageArea == null)
                {
                    storageArea = new StorageArea
                    {
                        AreaId = Guid.NewGuid(),
                        AreaCode = areaCode,
                        AreaName = areaCode,
                        IsActive = true,
                        CreatedAt = DateTime.Now
                    };
                    await _storageAreaRepository.AddAsync(storageArea, context.CancellationToken);
                }
                areaDict[areaCode] = storageArea;
            }

            // 循环将locations插入到MapNode
            foreach (var location in locations)
            {
                var useStatus = ParseUseStatus(location.use_status);
                var existingNode = await _mapNodeRepository.GetByMapIdAndNodeCodeAsync(
                    command.MapId, location.id, context.CancellationToken);

                Guid nodeId;
                if (existingNode != null)
                {
                    // 更新现有节点
                    nodeId = existingNode.NodeId;
                    existingNode.NodeName = location.alias;
                    existingNode.X = (location.position?.x != null ? (double)location.position.x : 0) * 1000;
                    existingNode.Y = (location.position?.y != null ? (double)location.position.y : 0) * 1000;
                    await _mapNodeRepository.UpdateAsync(existingNode, context.CancellationToken);
                }
                else
                {
                    // 创建新节点
                    nodeId = Guid.NewGuid();
                    var newNode = new MapNode
                    {
                        NodeId = nodeId,
                        MapId = command.MapId,
                        NodeCode = location.id,
                        NodeName = location.id,
                        X = (location.position?.x != null ? (double)location.position.x : 0) * 1000,
                        Y = (location.position?.y != null ? (double)location.position.y : 0) * 1000,
                        Type = MapNodeTYPE.store,
                        Active = true,
                        CreatedAt = DateTime.Now
                    };
                    await _mapNodeRepository.AddAsync(newNode, context.CancellationToken);
                }

                // 同步库位(跳过没有库区的)
                if (string.IsNullOrWhiteSpace(location.area) || !areaDict.TryGetValue(location.area, out var area))
                    continue;

                var existingLocation = await _storageLocationRepository.GetByLocationCodeAsync(location.id, context.CancellationToken);
                if (existingLocation != null)
                {
                    existingLocation.LocationName = location.alias;
                    existingLocation.Status = ParseStorageLocationStatus(useStatus);
                    existingLocation.UpdatedAt = DateTime.Now;
                    await _storageLocationRepository.UpdateAsync(existingLocation, context.CancellationToken);
                }
                else
                {
                    var newLocation = new StorageLocation
                    {
                        LocationId = Guid.NewGuid(),
                        AreaId = area.AreaId,
                        MapNodeId = nodeId,
                        LocationCode = location.id,
                        LocationName = location.alias,
                        Status = ParseStorageLocationStatus(useStatus),
                        IsActive = true,
                        CreatedAt = DateTime.Now
                    };
                    await _storageLocationRepository.AddAsync(newLocation, context.CancellationToken);
                }
            }

            // 删除远程不存在的节点及关联库位
            foreach (var node in existingNodes)
            {
                if (!remoteNodeCodes.Contains(node.NodeCode))
                {
                    // 先删除关联的库位
                    var locationToDelete = await _storageLocationRepository.GetByLocationCodeAsync(node.NodeCode, context.CancellationToken);
                    if (locationToDelete != null)
                        await _storageLocationRepository.DeleteAsync(locationToDelete, context.CancellationToken);

                    // 再删除节点
                    await _mapNodeRepository.DeleteAsync(node, context.CancellationToken);
                }
            }

            await context.RespondAsync(ApiResponse.Successful("同步地图资源成功"));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "同步地图资源失败: MapId={MapId}", command.MapId);
            await context.RespondAsync(ApiResponse.Failed($"同步失败: {ex.Message}"));
        }
    }

    /// <summary>
    /// 解析使用状态字符串为枚举
    /// @author zzy
    /// </summary>
    private static MapNodeUseStatus? ParseUseStatus(string? status)
    {
        return status switch
        {
            "use" => MapNodeUseStatus.use,
            "free" => MapNodeUseStatus.free,
            "pre_use" => MapNodeUseStatus.pre_use,
            _ => null
        };
    }

    /// <summary>
    /// 将节点使用状态转换为库位状态
    /// @author zzy
    /// </summary>
    private static StorageLocationStatus ParseStorageLocationStatus(MapNodeUseStatus? useStatus)
    {
        return useStatus switch
        {
            MapNodeUseStatus.use => StorageLocationStatus.Occupied,
            MapNodeUseStatus.pre_use => StorageLocationStatus.Reserved,
            _ => StorageLocationStatus.Empty
        };
    }
}