647556386
7 天以前 3fff7e8eb6fe78f3a512eff51bf16aee39cf9072
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs
@@ -1,12 +1,16 @@
using Microsoft.Extensions.Logging;
using Dm.filter;
using MailKit.Search;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Org.BouncyCastle.Asn1.Ocsp;
using SqlSugar;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
@@ -22,6 +26,7 @@
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Outbound;
namespace WIDESEA_BasicService
{
@@ -36,11 +41,19 @@
        private readonly IRepository<Dt_StockInfoDetail> _stockInfoDetailRepository;
        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
        private readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
        private readonly IRepository<Dt_PickingRecord> _pickingRecoreRepository;
        private readonly IMaterialUnitService _materialUnitService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly IOutboundOrderDetailService _outboundOrderDetailService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        public InvokeMESService(IHttpClientFactory httpClientFactory, ILogger<InvokeMESService> logger, IRepository<Dt_FeedbackToMes> feedbacktomesRepository, IRepository<Dt_StockInfoDetail> stockInfoDetailRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IOutStockLockInfoService outStockLockInfoService)
        private readonly IRepository<Dt_InterfaceLog> _interfacelogRepository;
        // å­˜å‚¨èµ„源ID及其对应的锁对象。使用 ConcurrentDictionary ç¡®ä¿å¯¹å­—典操作本身的线程安全。
        private static readonly ConcurrentDictionary<string, object> _resourceLocks = new ConcurrentDictionary<string, object>();
        // å…¨å±€é™æ€é”ï¼šç”¨äºŽä¿æŠ¤ _resourceLocks å­—典中 GetOrAdd æˆ– TryRemove æ—¶çš„竞争
        private static readonly object _globalLocker = new object();
        public InvokeMESService(IHttpClientFactory httpClientFactory, ILogger<InvokeMESService> logger, IRepository<Dt_FeedbackToMes> feedbacktomesRepository, IRepository<Dt_StockInfoDetail> stockInfoDetailRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IOutStockLockInfoService outStockLockInfoService, IMaterialUnitService materialUnitService, IRepository<Dt_PickingRecord> pickingRecoreRepository, IRepository<Dt_InterfaceLog> interfacelogRepository)
        {
            _httpClientFactory = httpClientFactory;
            _logger = logger;
@@ -51,6 +64,9 @@
            _outboundOrderService = outboundOrderService;
            _outboundOrderDetailService = outboundOrderDetailService;
            _outStockLockInfoService = outStockLockInfoService;
            _materialUnitService = materialUnitService;
            _pickingRecoreRepository = pickingRecoreRepository;
            _interfacelogRepository = interfacelogRepository;
        }
        /// <summary>
@@ -99,7 +115,7 @@
            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Add("Accept", "application/json");
            _logger.LogInformation("InvokeMESService  FeedbackOutbound :  " + json);
            _logger.LogInformation("InvokeMESService  FeedbackOutbound :  " + model.orderNo + "  , " + json);
            var response = await _client.PostAsync("AldMaterialOutbound/MaterialOutbound", content);
            string body = await response.Content.ReadAsStringAsync();
@@ -109,7 +125,7 @@
                throw new HttpRequestException(body);
            }
            _logger.LogInformation("InvokeMESService  FeedbackOutbound  body:  " + body);
            return JsonConvert.DeserializeObject<ResponseModel>(body);
        }
@@ -218,6 +234,8 @@
            }
        }
        /// <summary>
        /// 
        /// </summary>
@@ -226,314 +244,524 @@
        /// <returns></returns>
        public async Task<WebResponseContent> BatchOrderFeedbackToMes(List<string> orderNos, int inout)
        {
            if (inout == 1)
            {
                foreach (var orderNo in orderNos)
            try
            {
                if (inout == 1)
                {
                    try
                    foreach (var orderNo in orderNos)
                    {
                        var stockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
                                        .Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
                                        .ToList();
                        var feeds = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList();
                        var unreports = stockinfos.Where(x => !feeds.Contains(x.PalletCode)).ToList();
                        if (unreports != null && !unreports.Any())
                        try
                        {
                            return WebResponseContent.Instance.Error("没有需要回传的数据");
                        }
                        foreach (var item in unreports)
                        {
                            var lists = _stockInfoDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.StockId == item.Id).ToList();
                            if (lists.Any())
                            var stockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
                                            .Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
                                            .ToList();
                            var feeds = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList();
                            var unreports = stockinfos.Where(x => !feeds.Contains(x.PalletCode)).ToList();
                            if (unreports != null && !unreports.Any())
                            {
                                var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo);
                                if (inboundOrder != null)
                                return WebResponseContent.Instance.Error("没有需要回传的数据");
                            }
                            foreach (var item in unreports)
                            {
                                var lists = _stockInfoDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.StockId == item.Id).ToList();
                                if (lists.Any())
                                {
                                    if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库
                                    var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo);
                                    if (inboundOrder != null)
                                    {
                                        var allocate = SqlSugarHelper.DbWMS.Queryable<Dt_AllocateOrder>().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First();
                                        var allocatefeedmodel = new AllocateDto
                                        if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库
                                        {
                                            ReqCode = Guid.NewGuid().ToString(),
                                            ReqTime = DateTime.Now.ToString(),
                                            BusinessType = "2",
                                            FactoryArea = inboundOrder.FactoryArea,
                                            OperationType = 1,
                                            Operator = inboundOrder.Operator,
                                            OrderNo = inboundOrder.UpperOrderNo,
                                            fromWarehouse = allocate?.FromWarehouse ?? "",
                                            toWarehouse = allocate?.ToWarehouse ?? "",
                                            Details = new List<AllocateDtoDetail>()
                                            var allocate = SqlSugarHelper.DbWMS.Queryable<Dt_AllocateOrder>().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First();
                                            var allocatefeedmodel = new AllocateDto
                                            {
                                                ReqCode = Guid.NewGuid().ToString(),
                                                ReqTime = DateTime.Now.ToString(),
                                                BusinessType = "2",
                                                FactoryArea = inboundOrder.FactoryArea,
                                                OperationType = 1,
                                                Operator = inboundOrder.Operator,
                                                OrderNo = inboundOrder.UpperOrderNo,
                                                fromWarehouse = allocate?.FromWarehouse ?? "",
                                                toWarehouse = allocate?.ToWarehouse ?? "",
                                                Details = new List<AllocateDtoDetail>()
                                        };
                                            };
                                        var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                           .Select(group => new AllocateDtoDetail
                                           {
                                               MaterialCode = group.Key.MaterielCode,
                                               LineNo = group.Key.InboundOrderRowNo,
                                               WarehouseCode = group.Key.WarehouseCode,
                                               Qty = group.Sum(x => x.BarcodeQty),
                                               // warehouseCode= "1072",
                                               Unit = group.Key.BarcodeUnit,
                                               Barcodes = group.Select(row => new BarcodeInfo
                                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                               .Select(group => new AllocateDtoDetail
                                               {
                                                   Barcode = row.Barcode,
                                                   Qty = row.BarcodeQty,
                                                   BatchNo = row.BatchNo,
                                                   SupplyCode = row.SupplyCode,
                                                   Unit = row.Unit
                                               }).ToList()
                                           }).ToList();
                                        allocatefeedmodel.Details = groupedData;
                                                   MaterialCode = group.Key.MaterielCode,
                                                   LineNo = group.Key.InboundOrderRowNo,
                                                   WarehouseCode = group.Key.WarehouseCode,
                                                   Qty = group.Sum(x => x.BarcodeQty),
                                                   Unit = group.Key.BarcodeUnit,
                                                   Barcodes = group.Select(row => new BarcodeInfo
                                                   {
                                                       Barcode = row.Barcode,
                                                       Qty = row.BarcodeQty,
                                                       BatchNo = row.BatchNo,
                                                       SupplyCode = row.SupplyCode,
                                                       Unit = row.BarcodeUnit
                                                   }).ToList()
                                               }).ToList();
                                            allocatefeedmodel.Details = groupedData;
                                        var result = await FeedbackAllocate(allocatefeedmodel);
                                        if (result != null && result.code == 200)
                                        {
                                            _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                            var result = await FeedbackAllocate(allocatefeedmodel);
                                            if (result != null && result.code == 200)
                                            {
                                                _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                            }
                                        }
                                    }
                                    else
                                    {
                                        var feedmodel = new FeedbackInboundRequestModel
                                        else
                                        {
                                            reqCode = Guid.NewGuid().ToString(),
                                            reqTime = DateTime.Now.ToString(),
                                            business_type = inboundOrder.BusinessType,
                                            factoryArea = inboundOrder.FactoryArea,
                                            operationType = 1,
                                            Operator = inboundOrder.Operator,
                                            orderNo = inboundOrder.UpperOrderNo,
                                            status = inboundOrder.OrderStatus,
                                            details = new List<FeedbackInboundDetailsModel>()
                                            var feedmodel = new FeedbackInboundRequestModel
                                            {
                                                reqCode = Guid.NewGuid().ToString(),
                                                reqTime = DateTime.Now.ToString(),
                                                business_type = inboundOrder.BusinessType,
                                                factoryArea = inboundOrder.FactoryArea,
                                                operationType = 1,
                                                Operator = inboundOrder.Operator,
                                                orderNo = inboundOrder.UpperOrderNo,
                                                status = inboundOrder.OrderStatus,
                                                details = new List<FeedbackInboundDetailsModel>()
                                        };
                                            };
                                        var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                           .Select(group => new FeedbackInboundDetailsModel
                                           {
                                               materialCode = group.Key.MaterielCode,
                                               supplyCode = group.Key.SupplyCode,
                                               batchNo = group.Key.BatchNo,
                                               lineNo = group.Key.InboundOrderRowNo,
                                               qty = group.Sum(x => x.BarcodeQty),
                                               // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                                               warehouseCode = group.Key.WarehouseCode,
                                               unit = group.Key.BarcodeUnit,
                                               barcodes = group.Select(row => new FeedbackBarcodesModel
                                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode })
                                               .Select(group => new FeedbackInboundDetailsModel
                                               {
                                                   barcode = row.Barcode,
                                                   qty = row.BarcodeQty
                                               }).ToList()
                                           }).ToList();
                                        feedmodel.details = groupedData;
                                        var result = await FeedbackInbound(feedmodel);
                                        if (result != null && result.code == 200)
                                        {
                                            _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                                   materialCode = group.Key.MaterielCode,
                                                   supplyCode = group.Key.SupplyCode,
                                                   batchNo = group.Key.BatchNo,
                                                   lineNo = group.Key.InboundOrderRowNo,
                                                   qty = group.Sum(x => x.BarcodeQty),
                                                   // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                                                   warehouseCode = group.Key.WarehouseCode,
                                                   unit = group.Key.BarcodeUnit,
                                                   barcodes = group.Select(row => new FeedbackBarcodesModel
                                                   {
                                                       barcode = row.Barcode,
                                                       qty = row.BarcodeQty
                                                   }).ToList()
                                               }).ToList();
                                            feedmodel.details = groupedData;
                                            var result = await FeedbackInbound(feedmodel);
                                            if (result != null && result.code == 200)
                                            {
                                                _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes å›žå†™MES失败:  " + ex.Message);
                        return WebResponseContent.Instance.Error(ex.Message);
                    }
                        catch (Exception ex)
                        {
                            _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes å›žå†™MES失败:  " + ex.Message);
                            return WebResponseContent.Instance.Error(ex.Message);
                        }
                    }
                }
            }
            else if (inout == 2)
            {
                foreach (var orderNo in orderNos)
                else if (inout == 2)
                {
                    var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
                    if (outboundOrder != null && outboundOrder.IsBatch == 0)
                    foreach (var orderNo in orderNos)
                    {
                        await HandleOutboundOrderToMESCompletion(outboundOrder, orderNo);
                        var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
                        if (outboundOrder != null && outboundOrder.IsBatch == 0)
                        {
                            var result = await HandleOutboundOrderToMESCompletion(outboundOrder, orderNo);
                            return result;
                        }
                        else if (outboundOrder != null && outboundOrder.IsBatch == 1)
                        {
                            var result = await HandleOutboundOrderBatchToMESCompletion(outboundOrder, orderNo);
                            return result;
                        }
                    }
                    else if (outboundOrder != null && outboundOrder.IsBatch == 1)
                    {
                        await HandleOutboundOrderBatchToMESCompletion(outboundOrder, orderNo);
                    }
                }
                //}
                //else
                //{
                //    // æŠ¢é”å¤±è´¥ï¼šè¯´æ˜Žæœ‰å¦ä¸€ä¸ªçº¿ç¨‹ï¼ˆWCS回调或人工操作)正在处理
                //    return WebResponseContent.Instance.Error("WMS正在处理此回传任务,请勿重复操作。");
                //}
            }
            catch (Exception ex)
            {
                _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes :  " + ex.Message);
            }
            finally
            {
                // 2. ã€é‡Šæ”¾å†…存锁】无论成功失败,必须释放
                // MemoryLockManager.ReleaseLock(orderNos[0]);
            }
            return WebResponseContent.Instance.OK();
        }
        private async Task HandleOutboundOrderBatchToMESCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
        private async Task<WebResponseContent> HandleOutboundOrderBatchToMESCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // å®šä¹‰é»˜è®¤è¿”回(成功态)
            WebResponseContent response = WebResponseContent.Instance.OK("回传MES处理完成");
            //0 = æœªå›žä¼ ï¼Œ1 = å·²å›žä¼ æˆåŠŸï¼Œ2 = å›žä¼ å¤±è´¥
            try
            {
                if (outboundOrder.ReturnToMESStatus == 1)
                {
                    return;
                }
                // æ ¡éªŒï¼šå·²å›žä¼ ç›´æŽ¥è¿”回错误
                //if (outboundOrder.ReturnToMESStatus == 1)
                //{
                //    return WebResponseContent.Instance.Error("该单已经回传!");
                //}
                // æŸ¥è¯¢è®¢å•明细(仅查询未回传成功的)
                var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo && item.ReturnToMESStatus != 1)
                    .Select((o, item) => o)
                    .ToListAsync();
                var detailids = new List<int>();
                var allCompleted = true;
                foreach (var detail in orderDetails.Where(x => x.ReturnToMESStatus == 0).ToList())
                if (!orderDetails.Any())
                {
                    if (detail.OverOutQuantity >= detail.NeedOutQuantity)
                    return WebResponseContent.Instance.Error("暂无需要处理的订单明细");
                }
                var pickingRecords = await _pickingRecoreRepository.Db.Queryable<Dt_PickingRecord>().Where(x => x.OrderNo == orderNo && x.ReturnToMESStatus != 1 && !x.IsCancelled).ToListAsync();
                if (!pickingRecords.Any())
                    return WebResponseContent.Instance.Error("没有需要回传的分拣记录");
                var groups = pickingRecords.GroupBy(x => x.FeedBackMesDocumentNo).ToList();
                foreach (var group in groups)
                {
                    List<Dt_PickingRecord> records = group.ToList(); // è¯¥åˆ†ç»„下的所有记录
                    if (string.IsNullOrEmpty(group.Key))
                    {
                        detailids.Add(detail.Id);
                        var emptydocumentNo = UniqueValueGenerator.Generate();
                        records.ForEach(x => { x.FeedBackMesDocumentNo = emptydocumentNo; });
                        var result = await _pickingRecoreRepository.Db.Updateable(records).ExecuteCommandAsync();
                        var interfacelog = new Dt_InterfaceLog
                        {
                            Content = JsonConvert.SerializeObject(records),
                            DocumentNo = emptydocumentNo,
                            OrderNo = orderNo,
                            OrderType = "2",
                        };
                        _interfacelogRepository.AddData(interfacelog);
                        if (result > 0)
                        {
                            (bool _flowControl, WebResponseContent _value) = await FeedBackBatchToMes(outboundOrder, orderNo, orderDetails, pickingRecords, emptydocumentNo);
                            return _value;
                        }
                    }
                    else
                    {
                        allCompleted = false;
                    }
                }
                if (orderDetails.Any(x => x.ReturnToMESStatus == 2))
                {
                    allCompleted = false;
                }
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                }
                var documentno = UniqueValueGenerator.Generate();
                var feedmodel = new FeedbackOutboundRequestModel
                {
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    business_type = outboundOrder.BusinessType,
                    factoryArea = outboundOrder.FactoryArea,
                    operationType = 1,
                    Operator = outboundOrder.Operator,
                    orderNo = outboundOrder.UpperOrderNo,
                    documentsNO = documentno,
                    status = outboundOrder.OrderStatus,
                    details = new List<FeedbackOutboundDetailsModel>()
                };
                foreach (var detail in orderDetails.Where(x => detailids.Contains(x.Id)).ToList())
                {
                    // èŽ·å–è¯¥æ˜Žç»†å¯¹åº”çš„æ¡ç ä¿¡æ¯ï¼ˆä»Žé”å®šè®°å½•ï¼‰
                    var detailLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderNo == orderNo && detailids.Contains(x.OrderDetailId) &&
                                   x.Status == (int)OutLockStockStatusEnum.拣选完成)
                        .ToListAsync();
                    var detailModel = new FeedbackOutboundDetailsModel
                    {
                        materialCode = detail.MaterielCode,
                        lineNo = detail.lineNo, // æ³¨æ„ï¼šè¿™é‡Œå¯èƒ½éœ€è¦è°ƒæ•´å­—段名
                        warehouseCode = detail.WarehouseCode,
                        qty = detail.OverOutQuantity, // ä½¿ç”¨è®¢å•明细的已出库数量
                        currentDeliveryQty = detail.OverOutQuantity,
                        unit = detail.Unit,
                        barcodes = detailLocks.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel
                        var ilog = _interfacelogRepository.QueryFirst(x => x.DocumentNo == group.Key);
                        if (ilog == null)
                        {
                            barcode = lockInfo.CurrentBarcode,
                            supplyCode = lockInfo.SupplyCode,
                            batchNo = lockInfo.BatchNo,
                            unit = lockInfo.Unit,
                            qty = lockInfo.PickedQty // æ¡ç çº§åˆ«çš„æ•°é‡ä»ç”¨é”å®šè®°å½•
                        }).ToList()
                    };
                    feedmodel.details.Add(detailModel);
                }
                var result = await FeedbackOutbound(feedmodel);
                if (result != null && result.code == 200)
                {
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                           .SetColumns(it => new Dt_OutboundOrderDetail
                           {
                               ReturnToMESStatus = 1,
                               documentsNO = documentno,
                           })
                        .Where(x => detailids.Contains(x.Id))
                        .ExecuteCommandAsync();
                    if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                    {
                        await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                          .SetColumns(x => x.ReturnToMESStatus == 1)
                          .Where(x => x.OrderNo == orderNo)
                          .ExecuteCommandAsync();
                    }
                    else
                    {
                        var dborderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                              .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                              .Where((o, item) => item.OrderNo == orderNo && item.ReturnToMESStatus != 1)
                              .Select((o, item) => o)
                              .ToListAsync();
                        var secallCompleted = true;
                        foreach (var detail in orderDetails.Where(x => x.ReturnToMESStatus == 0).ToList())
                        {
                            if (detail.OverOutQuantity < detail.NeedOutQuantity)
                            var interfacelog = new Dt_InterfaceLog
                            {
                                secallCompleted = false;
                            }
                                Content = JsonConvert.SerializeObject(records),
                                DocumentNo = group.Key,
                                OrderNo = orderNo,
                                OrderType = "2",
                            };
                            _interfacelogRepository.AddData(interfacelog);
                        }
                        if (secallCompleted)
                        {
                            await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                                 .SetColumns(it => new Dt_OutboundOrder
                                 {
                                     ReturnToMESStatus = 1,
                                     OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt(),
                                 })
                                 .Where(x => x.OrderNo == orderNo)
                                 .ExecuteCommandAsync();
                        }
                        (bool _flowControl, WebResponseContent _value) = await FeedBackBatchToMes(outboundOrder, orderNo, orderDetails, pickingRecords, group.Key);
                        return _value;
                    }
                }
                else
                {
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                         .SetColumns(it => new Dt_OutboundOrderDetail
                         {
                             ReturnToMESStatus = 2,
                             documentsNO = documentno,
                         })
                      .Where(x => detailids.Contains(x.Id))
                      .ExecuteCommandAsync();
                }
                //var documentNo = UniqueValueGenerator.Generate();
                //(bool flowControl, WebResponseContent value) = await FeedBackBatchToMes(outboundOrder, orderNo, orderDetails, pickingRecords, documentNo);
                //if (!flowControl)
                //{
                //    return value;
                //}
                // å›žä¼ æˆåŠŸçš„æœ€ç»ˆè¿”å›ž
                response = WebResponseContent.Instance.OK($"回传MES成功,单据号:{orderNo}");
            }
            catch (Exception ex)
            {
                _logger.LogError($"CheckAndUpdateOrderStatus失败 - OrderNo: {orderNo}, Error: {ex.Message}");
                // å…¨å±€å¼‚常捕获:记录详细日志 + è¿”回错误
                string errorMsg = $"处理出库单回传MES时发生异常 - OrderNo: {orderNo}, Error: {ex.Message}, StackTrace: {ex.StackTrace}";
                _logger.LogError(ex, errorMsg); // è®°å½•带异常堆栈的日志
                // å¼‚常返回(给前端的友好提示,隐藏堆栈信息)
                response = WebResponseContent.Instance.Error("处理回传MES时发生异常,请联系管理员");
            }
            return response;
        }
        private async Task HandleOutboundOrderToMESCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        private async Task<(bool flowControl, WebResponseContent value)> FeedBackBatchToMes(Dt_OutboundOrder outboundOrder, string orderNo, List<Dt_OutboundOrderDetail> orderDetails, List<Dt_PickingRecord> pickingRecords, string documentNo)
        {
            var feedModel = new FeedbackOutboundRequestModel
            {
                reqCode = Guid.NewGuid().ToString(),
                reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                business_type = outboundOrder.BusinessType,
                factoryArea = outboundOrder.FactoryArea,
                operationType = 1,
                Operator = outboundOrder.Operator != "" ? outboundOrder.Operator : App.User.UserName,
                orderNo = outboundOrder.UpperOrderNo,
                documentsNO = documentNo,
                status = outboundOrder.OrderStatus,
                details = new List<FeedbackOutboundDetailsModel>()
            };
            var detailIds = new List<int>();
            // å¡«å……明细和条码信息
            foreach (var detail in orderDetails)
            {
                // æŸ¥è¯¢è¯¥æ˜Žç»†å¯¹åº”的锁定条码记录
                var detailPicks = pickingRecords.Where(x => x.OrderNo == orderNo
                               && detail.Id == x.OrderDetailId).ToList();
                if (!detailPicks.Any())
                {
                    continue;
                }
                var detailModel = new FeedbackOutboundDetailsModel
                {
                    materialCode = detail.MaterielCode,
                    lineNo = detail.lineNo,
                    warehouseCode = detail.WarehouseCode,
                    qty = detail.BarcodeQty,
                    currentDeliveryQty = 0,
                    unit = detail.BarcodeUnit,
                    barcodes = new List<WIDESEA_DTO.Outbound.BarcodesModel>()
                };
                // å¡«å……条码信息(含单位转换)
                foreach (var item in detailPicks)
                {
                    if (item.PickQuantity <= 0)
                    {
                        continue;
                    }
                    var barModel = new WIDESEA_DTO.Outbound.BarcodesModel
                    {
                        barcode = item.Barcode,
                        supplyCode = item.SupplyCode,
                        batchNo = item.BatchNo,
                        unit = item.BarcodeUnit,
                        qty = item.PickQuantity
                    };
                    // å•位不一致时转换
                    if (detail.BarcodeUnit != detail.Unit)
                    {
                        var convertResult = await _materialUnitService.ConvertAsync(
                            item.MaterielCode, item.PickQuantity, detail.Unit, detail.BarcodeUnit);
                        barModel.unit = convertResult.Unit;
                        barModel.qty = convertResult.Quantity;
                    }
                    else
                    {
                        barModel.qty = item.PickQuantity;
                    }
                    detailModel.currentDeliveryQty += barModel.qty;
                    detailModel.barcodes.Add(barModel);
                }
                detailIds.Add(detail.Id);
                feedModel.details.Add(detailModel);
            }
            feedModel.details = feedModel.details.GroupBy(item => new { item.materialCode, item.lineNo, item.warehouseCode, item.unit, item.qty }).Select(group => new FeedbackOutboundDetailsModel
            {
                materialCode = group.Key.materialCode,
                lineNo = group.Key.lineNo,
                warehouseCode = group.Key.warehouseCode,
                qty = group.Key.qty,
                currentDeliveryQty = group.Sum(x => x.currentDeliveryQty),
                unit = group.Key.unit,
                barcodes = group.SelectMany(x => x.barcodes.GroupBy(o => new { o.barcode, o.supplyCode, o.batchNo, o.unit }).Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                {
                    barcode = row.Key.barcode,
                    supplyCode = row.Key.supplyCode,
                    batchNo = row.Key.batchNo,
                    unit = row.Key.unit,
                    qty = row.Sum(y => y.qty)
                })).ToList()
            }).ToList();
            var allCompleted = true;
            // ç­›é€‰å¾…回传的明细(ReturnToMESStatus=0)
            var pendingDetails = orderDetails.Where(x => x.ReturnToMESStatus == 0).ToList();
            foreach (var detail in pendingDetails)
            {
                if (detail.OverOutQuantity < detail.NeedOutQuantity)
                {
                    allCompleted = false;
                }
            }
            // å­˜åœ¨å›žä¼ å¤±è´¥çš„æ˜Žç»†ï¼ˆReturnToMESStatus=2),标记未完成
            if (orderDetails.Any(x => x.ReturnToMESStatus == 2))
            {
                allCompleted = false;
            }
            //  æ›´æ–°è®¢å•状态
            int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
            if (allCompleted && outboundOrder.OrderStatus != newStatus)
            {
                int updateCount = await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => x.OrderStatus == newStatus)
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
                if (updateCount <= 0)
                {
                    _logger.LogWarning($"更新出库单状态失败 - OrderNo: {orderNo}, ç›®æ ‡çŠ¶æ€: {newStatus}");
                }
            }
            // è°ƒç”¨MES回传接口
            var mesResult = await FeedbackOutbound(feedModel);
            if (mesResult == null || mesResult.code != 200)
            {
                // æ›´æ–°æ˜Žç»†ä¸ºå›žä¼ å¤±è´¥ï¼ˆReturnToMESStatus=2)
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        ReturnToMESStatus = 2,
                        documentsNO = documentNo,
                    })
                    .Where(x => detailIds.Contains(x.Id))
                    .ExecuteCommandAsync();
                return (flowControl: false, value: WebResponseContent.Instance.Error($"回传MES失败"));
            }
            foreach (var record in pickingRecords.Where(x => detailIds.Contains(x.OrderDetailId)).ToList())
            {
                record.ReturnToMESStatus = 1;
            }
            var updates = pickingRecords.Where(x => detailIds.Contains(x.OrderDetailId)).ToList();
            updates.ForEach(x =>
            {
                x.ReturnToMESStatus = 1;
            });
            await _pickingRecoreRepository.Db.Updateable(updates).ExecuteCommandAsync();
            await _interfacelogRepository.Db.Updateable<Dt_InterfaceLog>()
                                             .SetColumns(x => x.ReturnToMESStatus == 1)
                                             .Where(x => x.OrderNo == orderNo)
                                             .ExecuteCommandAsync();
            if (allCompleted)
            {
                //MES回传成功:更新明细为回传成功状态
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        ReturnToMESStatus = 1,
                        documentsNO = documentNo,
                    })
                    .Where(x => detailIds.Contains(x.Id))
                    .ExecuteCommandAsync();
            }
            // æ ¡éªŒæ˜¯å¦æ‰€æœ‰æ˜Žç»†éƒ½å®Œæˆï¼Œæ›´æ–°è®¢å•最终状态
            if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder
                    {
                        ReturnToMESStatus = 1,
                        OrderStatus = newStatus
                    })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
            else
            {
                // äºŒæ¬¡æ ¡éªŒæ˜¯å¦æ‰€æœ‰æœªå›žä¼ æ˜Žç»†éƒ½å·²å®Œæˆ
                var dbOrderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo && item.ReturnToMESStatus != 1)
                    .Select((o, item) => o)
                    .ToListAsync();
                var secAllCompleted = true;
                foreach (var detail in dbOrderDetails.Where(x => x.ReturnToMESStatus == 0).ToList())
                {
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        secAllCompleted = false;
                        break;
                    }
                }
                if (secAllCompleted)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(it => new Dt_OutboundOrder
                        {
                            ReturnToMESStatus = 1,
                            OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt(),
                        })
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                }
            }
            return (flowControl: true, value: null);
        }
        private async Task<WebResponseContent> HandleOutboundOrderToMESCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // å‰ç½®å‚数校验:空值直接返回错误
            if (outboundOrder == null)
            {
                return WebResponseContent.Instance.Error("出库单实体为空,无法处理回传MES");
            }
            if (string.IsNullOrWhiteSpace(orderNo))
            {
                return WebResponseContent.Instance.Error("订单号为空,无法处理回传MES");
            }
            try
            {
                if (outboundOrder.ReturnToMESStatus == 1)
                {
                    return;
                    return WebResponseContent.Instance.Error($"OrderNo: {orderNo}, è¯¥å•已经回传!");
                }
                var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo)
                    .Select((o, item) => o)
                    .ToListAsync();
                // æ— æ˜Žç»†åœºæ™¯è¿”回警告
                if (!orderDetails.Any())
                {
                    return WebResponseContent.Instance.Error($"OrderNo: {orderNo} æœªæŸ¥è¯¢åˆ°è®¢å•明细");
                }
                // åˆ¤æ–­æ˜¯å¦æ‰€æœ‰æ˜Žç»†å®Œæˆå‡ºåº“
                bool allCompleted = true;
                foreach (var detail in orderDetails)
                {
@@ -544,17 +772,17 @@
                    }
                }
                //  æ›´æ–°è®¢å•状态(修正语法错误:== â†’ =)
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .SetColumns(x => x.OrderStatus == newStatus) // å…³é”®ä¿®æ­£ï¼šèµ‹å€¼è€Œéžåˆ¤æ–­
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                }
                //只有正常分拣完成时才向MES反馈
                // ä»…分拣完成时向MES反馈
                if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                {
                    var feedmodel = new FeedbackOutboundRequestModel
@@ -571,40 +799,47 @@
                        details = new List<FeedbackOutboundDetailsModel>()
                    };
                    // æž„建明细数据
                    foreach (var detail in orderDetails)
                    {
                        // èŽ·å–è¯¥æ˜Žç»†å¯¹åº”çš„æ¡ç ä¿¡æ¯ï¼ˆä»Žé”å®šè®°å½•ï¼‰
                        var detailLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                            .Where(x => x.OrderNo == orderNo &&
                                       x.OrderDetailId == detail.Id &&
                                       x.Status == (int)OutLockStockStatusEnum.拣选完成)
                                       (x.Status == (int)OutLockStockStatusEnum.拣选完成 || x.Status == (int)OutLockStockStatusEnum.已回库))
                            .ToListAsync();
                        var detailModel = new FeedbackOutboundDetailsModel
                        {
                            materialCode = detail.MaterielCode,
                            lineNo = detail.lineNo, // æ³¨æ„ï¼šè¿™é‡Œå¯èƒ½éœ€è¦è°ƒæ•´å­—段名
                            warehouseCode = detail.WarehouseCode,
                            qty = detail.OverOutQuantity, // ä½¿ç”¨è®¢å•明细的已出库数量
                            currentDeliveryQty = detail.OverOutQuantity,
                            unit = detail.Unit,
                            barcodes = detailLocks.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel
                        var groupdata = detailLocks.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
                            .Select(group => new FeedbackOutboundDetailsModel
                            {
                                barcode = lockInfo.CurrentBarcode,
                                supplyCode = lockInfo.SupplyCode,
                                batchNo = lockInfo.BatchNo,
                                unit = lockInfo.Unit,
                                qty = lockInfo.PickedQty // æ¡ç çº§åˆ«çš„æ•°é‡ä»ç”¨é”å®šè®°å½•
                            }).ToList()
                        };
                                materialCode = group.Key.MaterielCode,
                                lineNo = group.Key.lineNo,
                                warehouseCode = group.Key.WarehouseCode,
                                qty = group.Sum(x => x.PickedQty),
                                currentDeliveryQty = group.Sum(x => x.PickedQty),
                                unit = group.Key.BarcodeUnit,
                                barcodes = group.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel
                                {
                                    barcode = lockInfo.CurrentBarcode,
                                    supplyCode = lockInfo.SupplyCode,
                                    batchNo = lockInfo.BatchNo,
                                    unit = lockInfo.BarcodeUnit,
                                    qty = lockInfo.PickedQty
                                }).ToList()
                            }).ToList();
                        feedmodel.details.Add(detailModel);
                        feedmodel.details.AddRange(groupdata);
                    }
                    //  è°ƒç”¨MES接口
                    var result = await FeedbackOutbound(feedmodel);
                    if (result != null && result.code == 200)
                    if (result == null)
                    {
                        return WebResponseContent.Instance.Error($"OrderNo: {orderNo} MES回传接口返回空");
                    }
                    if (result.code == 200)
                    {
                        //  å›žä¼ æˆåŠŸï¼šæ›´æ–°å›žä¼ çŠ¶æ€
                        await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                            .SetColumns(x => x.ReturnToMESStatus == 1)
                            .Where(x => x.OrderId == outboundOrder.Id)
@@ -614,16 +849,31 @@
                            .SetColumns(x => x.ReturnToMESStatus == 1)
                            .Where(x => x.OrderNo == orderNo)
                            .ExecuteCommandAsync();
                        await _pickingRecoreRepository.Db.Updateable<Dt_PickingRecord>()
                                 .SetColumns(x => x.ReturnToMESStatus == 1)
                            .Where(x => x.OrderNo == orderNo)
                            .ExecuteCommandAsync();
                        return WebResponseContent.Instance.OK("回传MES成功");
                    }
                    else
                    {
                        var errorMsg = $"OrderNo: {orderNo} å›žä¼ MES失败,错误码:{result.code},错误信息:{result.message ?? "无"}";
                        _logger.LogError(errorMsg);
                        return WebResponseContent.Instance.Error(errorMsg);
                    }
                }
                return WebResponseContent.Instance.OK("订单状态已更新,未完全完成分拣无需回传MES");
            }
            catch (Exception ex)
            {
                _logger.LogError($"CheckAndUpdateOrderStatus失败 - OrderNo: {orderNo}, Error: {ex.Message}");
                // å…¨å±€å¼‚常捕获:返回错误+记录详细日志
                var errorMsg = $"OrderNo: {orderNo} å¤„理回传MES时发生异常:{ex.Message}";
                _logger.LogError(ex, errorMsg); // è®°å½•堆栈信息便于排查
                return WebResponseContent.Instance.Error("处理回传MES时发生异常,请联系管理员");
            }
        }
    }
    public static class UniqueValueGenerator
@@ -638,14 +888,214 @@
        public static string Generate()
        {
            var now = DateTime.Now;
            string datePart = now.ToString("yyyyMMdd");
            string datePart = now.ToString("MMdd");
            long ticksPart = now.Ticks;
            // æ‹¼æŽ¥ï¼šè®¡æ•°å™¨è¡¥0为3位(避免位数不一致)
            return $"{datePart}{ticksPart}";
        }
        public static string GenerateCount()
        {
            var now = DateTime.Now;
            string datePart = now.ToString("yyyyMMddHHmmss");
            // åŽŸå­é€’å¢žè®¡æ•°å™¨ï¼ˆå–æ¨¡1000,确保计数器仅3位,控制长度)
            long counterPart = Interlocked.Increment(ref _counter) % 1000;
            // æ‹¼æŽ¥ï¼šè®¡æ•°å™¨è¡¥0为3位(避免位数不一致)
            return $"{datePart}{ticksPart}{counterPart:D3}";
            return $"{datePart}{counterPart:D3}";
        }
    }
    public static class MemoryLockManager
    {
        // å­˜å‚¨èµ„源锁的元数据(锁对象、持有线程、占用时间、超时时间)
        private class LockMetadata
        {
            public object LockObject { get; } = new object();
            public int HoldingThreadId { get; set; } = -1; // æŒæœ‰é”çš„线程ID
            public DateTime AcquireTime { get; set; } // èŽ·å–é”çš„æ—¶é—´
            public TimeSpan Timeout { get; set; } // é”è¶…æ—¶æ—¶é—´
            public bool IsReleased { get; set; } // æ˜¯å¦å·²é‡Šæ”¾
        }
        // èµ„源ID -> é”å…ƒæ•°æ®
        private static readonly ConcurrentDictionary<string, LockMetadata> _resourceLocks = new();
        // å…¨å±€é”ï¼ˆä¿æŠ¤é”å…ƒæ•°æ®çš„创建/删除)
        private static readonly object _globalLocker = new();
        // éšæœºæ•°ç”Ÿæˆå™¨ï¼ˆç”¨äºŽç”Ÿæˆ3-5秒随机超时)
        private static readonly Random _random = new Random();
        /// <summary>
        /// å°è¯•锁定资源(带超时自动释放)
        /// </summary>
        /// <param name="resourceId">资源ID</param>
        /// <param name="timeoutSeconds">超时时间(默认3-5秒随机)</param>
        /// <returns>是否成功获取锁</returns>
        public static bool TryAcquireLock(string resourceId, int? timeoutSeconds = null)
        {
            if (string.IsNullOrEmpty(resourceId))
                throw new ArgumentNullException(nameof(resourceId));
            // ç¡®å®šè¶…时时间(3-5秒随机)
            var timeout = TimeSpan.FromSeconds(timeoutSeconds ?? _random.Next(3, 6));
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            LockMetadata lockMeta = null;
            lock (_globalLocker)
            {
                // èŽ·å–æˆ–åˆ›å»ºé”å…ƒæ•°æ®
                lockMeta = _resourceLocks.GetOrAdd(resourceId, key => new LockMetadata());
                // é˜²æ­¢é‡å¤èŽ·å–ï¼ˆå½“å‰çº¿ç¨‹å·²æŒæœ‰é”ï¼‰
                if (lockMeta.HoldingThreadId == currentThreadId && !lockMeta.IsReleased)
                    return true; // çº¿ç¨‹å¯é‡å…¥
            }
            // å°è¯•获取锁(非阻塞)
            if (!Monitor.TryEnter(lockMeta.LockObject))
                return false;
            // æ ‡è®°é”æŒæœ‰çŠ¶æ€
            lockMeta.HoldingThreadId = currentThreadId;
            lockMeta.AcquireTime = DateTime.Now;
            lockMeta.Timeout = timeout;
            lockMeta.IsReleased = false;
            // å¯åŠ¨è¶…æ—¶è‡ªåŠ¨é‡Šæ”¾ä»»åŠ¡
            _ = Task.Delay(timeout).ContinueWith(_ =>
            {
                try
                {
                    ReleaseLock(resourceId, force: true);
                }
                catch (Exception ex)
                {
                    // è®°å½•超时释放异常
                    Console.WriteLine($"资源[{resourceId}]超时自动释放失败:{ex.Message}");
                }
            });
            return true;
        }
        /// <summary>
        /// é‡Šæ”¾èµ„源锁
        /// </summary>
        /// <param name="resourceId">资源ID</param>
        /// <param name="force">是否强制释放(超时自动释放时使用)</param>
        public static void ReleaseLock(string resourceId, bool force = false)
        {
            if (string.IsNullOrEmpty(resourceId))
                throw new ArgumentNullException(nameof(resourceId));
            if (!_resourceLocks.TryGetValue(resourceId, out var lockMeta))
                return;
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            // æ ¡éªŒé‡Šæ”¾åˆæ³•性:仅持有锁的线程或强制释放可执行
            if (!force && lockMeta.HoldingThreadId != currentThreadId)
            {
                // éžæŒæœ‰çº¿ç¨‹å°è¯•释放,抛出异常或返回(根据业务选择)
                throw new InvalidOperationException($"线程[{currentThreadId}]无权释放资源[{resourceId}]的锁(当前持有线程:{lockMeta.HoldingThreadId})");
            }
            // åŒé‡æ ¡éªŒé”çŠ¶æ€
            lock (_globalLocker)
            {
                if (lockMeta.IsReleased)
                    return;
                // ç¡®ä¿é”è¢«å½“前线程持有(强制释放除外)
                if (Monitor.IsEntered(lockMeta.LockObject))
                {
                    try
                    {
                        Monitor.Exit(lockMeta.LockObject);
                    }
                    catch (SynchronizationLockException)
                    {
                        // å·²è¢«é‡Šæ”¾ï¼Œå¿½ç•¥
                        return;
                    }
                }
                // æ ‡è®°é”å·²é‡Šæ”¾
                lockMeta.IsReleased = true;
                lockMeta.HoldingThreadId = -1;
                // å»¶è¿Ÿæ¸…理锁元数据(避免并发创建)
                // ç­‰å¾…1秒后清理,防止刚释放就有新线程抢锁导致重复创建
                _ = Task.Delay(1000).ContinueWith(_ =>
                {
                    lock (_globalLocker)
                    {
                        // ä»…当锁未被重新持有且已释放时清理
                        if (_resourceLocks.TryGetValue(resourceId, out var meta)
                            && meta.IsReleased
                            && meta.HoldingThreadId == -1)
                        {
                            _resourceLocks.TryRemove(resourceId, out var _resid);
                        }
                    }
                });
            }
        }
        /// <summary>
        /// æ£€æŸ¥èµ„源是否被锁定
        /// </summary>
        public static bool IsLocked(string resourceId)
        {
            if (!_resourceLocks.TryGetValue(resourceId, out var meta))
                return false;
            return !meta.IsReleased && meta.HoldingThreadId != -1;
        }
        public static void TestUsed()
        {
            string orderNo = "testt";
            bool lockAcquired = false;
            try
            {
                // å°è¯•获取锁(自动3-5秒超时)
                lockAcquired = MemoryLockManager.TryAcquireLock(orderNo);
                if (lockAcquired)
                {
                    // æ‰§è¡Œä¸šåŠ¡é€»è¾‘ï¼ˆå¦‚å¤„ç†è®¢å•ï¼‰
                    Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]获取锁:{orderNo}");
                    // æ¨¡æ‹Ÿä¸šåŠ¡è€—æ—¶ï¼ˆæµ‹è¯•è¶…æ—¶é‡Šæ”¾ï¼‰
                    // Thread.Sleep(6000);
                }
                else
                {
                    Console.WriteLine($"资源[{orderNo}]被占用,获取锁失败");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"业务处理异常:{ex.Message}");
            }
            finally
            {
                // é‡Šæ”¾é”ï¼ˆä»…当成功获取时)
                if (lockAcquired)
                {
                    try
                    {
                        MemoryLockManager.ReleaseLock(orderNo);
                        Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]释放锁:{orderNo}");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"释放锁失败:{ex.Message}");
                    }
                }
            }
        }
    }
}