yanjinhui
2 天以前 fe596f9db05103917c9257348edcbd3ecb5b46e8
Merge branch 'master' of http://115.159.85.185:8098/r/RuiShengZhiNeng/GaoPuLiTiKu
已添加4个文件
已修改7个文件
1190 ■■■■■ 文件已修改
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_BasicInfoService/ApiInfoService.cs 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/BasicInfo/Dt_InterfaceLog.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/WMS/WMSController.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/ApiLogMiddlewareNew.cs 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/LogCleanupService.cs 426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/LoggerNew.cs 431 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/MiddlewareHelpersNew.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Program.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/WIDESEAWCS_Server.csproj 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
代码管理/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/Task/RGVTaskExtend.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_BasicInfoService/ApiInfoService.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using WIDESEAWCS_Core;
@@ -36,11 +37,15 @@
        {
            WebResponseContent content = new WebResponseContent();
            string response = string.Empty;
            string requestHash = string.Empty;
            string responseHash = string.Empty;
            Dt_ApiInfo? apiInfo = null;
            try
            {
                apiInfo = BaseDal.QueryFirst(x => x.ApiCode == apiCode) ?? throw new Exception($"未找到{remark}接口配置信息!请检查接口配置");
                response = HttpHelper.Post(apiInfo.ApiAddress, requestParameters);
                requestHash = ComputeHash(requestParameters);
                responseHash = ComputeHash(response);
                if (apiInfo.Remark.Contains("四向车"))
                {
                    FOURBOTReturn fOURBOTReturn = response.DeserializeObject<FOURBOTReturn>();
@@ -70,24 +75,63 @@
            {
                if (isAdd && !string.IsNullOrEmpty(response) && apiInfo != null)
                {
                    Dt_InterfaceLog interfaceLog = new Dt_InterfaceLog()
                    #region æŸ¥è¯¢å½“天是否存在相同记录
                    var today = DateTime.Now.Date;
                    var existingLog = _interfaceLogService.Repository.QueryFirst(x =>
                        x.ApiCode == apiCode &&
                        x.RequestParametersHash == requestHash &&
                        x.ResponseParametersHash == responseHash &&
                        x.CreateDate.Date == today);
                    if (existingLog != null)
                    {
                        ApiCode = apiCode,
                        RequestParameters = requestParameters,
                        ApiAddress = apiInfo.ApiAddress,
                        ApiName = apiInfo.ApiName,
                        PushFrequency = 1,
                        PushState = content.Status ? 1 : 2,
                        Requestor = "WCS",
                        Recipient = apiInfo.Remark,
                        ResponseParameters = response,
                        Creater = "System",
                        Remark = content.Status ? remark : content.Message,
                    };
                    _interfaceLogService.Repository.AddData(interfaceLog);
                        existingLog.PushFrequency = existingLog.PushFrequency + 1;
                        existingLog.PushState = content.Status ? 1 : 2;
                        existingLog.ResponseParameters = response;
                        existingLog.Remark = content.Status ? remark : content.Message;
                        existingLog.Modifier = App.User?.UserName ?? "System";
                        existingLog.ModifyDate = DateTime.Now;
                        _interfaceLogService.Repository.UpdateData(existingLog);
                    }
                    #endregion
                    else
                    {
                        Dt_InterfaceLog interfaceLog = new Dt_InterfaceLog()
                        {
                            ApiCode = apiCode,
                            RequestParameters = requestParameters,
                            ApiAddress = apiInfo.ApiAddress,
                            ApiName = apiInfo.ApiName,
                            RequestParametersHash = requestHash,
                            ResponseParametersHash = responseHash,
                            PushFrequency = 1,
                            PushState = content.Status ? 1 : 2,
                            Requestor = "WCS",
                            Recipient = apiInfo.Remark,
                            ResponseParameters = response,
                            Creater = "System",
                            Remark = content.Status ? remark : content.Message,
                        };
                        _interfaceLogService.Repository.AddData(interfaceLog);
                    }
                }
            }
            return content;
        }
        /// <summary>
        /// è®¡ç®—字符串的SHA256哈希值
        /// </summary>
        private static string ComputeHash(string input)
        {
            if (string.IsNullOrEmpty(input))
                return string.Empty;
            using (SHA256 sha256 = SHA256.Create())
            {
                byte[] bytes = Encoding.UTF8.GetBytes(input);
                byte[] hashBytes = sha256.ComputeHash(bytes);
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
            }
        }
    }
}
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/BasicInfo/Dt_InterfaceLog.cs
@@ -51,6 +51,12 @@
        public int PushState { get; set; } = 0;
        /// <summary>
        /// è¯·æ±‚方用户IP
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 20, ColumnDescription = "请求方用户IP")]
        public string RequestorUserIP { get; set; }
        /// <summary>
        /// è¯·æ±‚æ–¹
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 50, ColumnDescription = "请求方")]
@@ -75,6 +81,18 @@
        public string ResponseParameters { get; set; }
        /// <summary>
        /// è¯·æ±‚参数哈希值(用于索引和去重)
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 64, ColumnDescription = "请求参数哈希值")]
        public string RequestParametersHash { get; set; }
        /// <summary>
        /// å“åº”参数哈希值(用于索引和去重)
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 64, ColumnDescription = "响应参数哈希值")]
        public string ResponseParametersHash { get; set; }
        /// <summary>
        /// å¤‡æ³¨
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 100, ColumnDescription = "备注")]
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/WMS/WMSController.cs
@@ -71,7 +71,7 @@
        }
        /// <summary>
        /// è´¨æ£€ç»“果反馈
        /// å¤–检结果反馈
        /// </summary>
        /// <param name="iQC"></param>
        /// <returns></returns>
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/ApiLogMiddlewareNew.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
namespace WIDESEAWCS_Core.Middlewares
{
    /// <summary>
    /// è®°å½•请求和响应数据
    /// </summary>
    public class ApiLogMiddlewareNew
    {
        /// <summary>
        ///
        /// </summary>
        private readonly RequestDelegate _next;
        public ApiLogMiddlewareNew(RequestDelegate next, ILogger<ApiLogMiddlewareNew> logger)
        {
            _next = next;
        }
        //todo
        public async Task InvokeAsync(HttpContext context)
        {
            if (App.ExpDateTime != null && (DateTime.Now - App.ExpDateTime.GetValueOrDefault()).TotalSeconds > 0)
            {
                context.Response.StatusCode = HttpStatusCode.InternalServerError.ObjToInt();
                context.Response.ContentType = "application/json";
                var json = new WebResponseContent();
                json.Message = HttpStatusCode.InternalServerError.ToString();//错误信息
                json.Code = 500;//500异常
                StreamWriter streamWriter = new StreamWriter(context.Response.Body);
                await streamWriter.WriteAsync(json.Serialize());
                return;
            }
            // è¿‡æ»¤ï¼Œåªæœ‰æŽ¥å£
            if (context.Request.Path.Value?.Contains("api") ?? false)
            {
                context.Request.EnableBuffering();
                Stream originalBody = context.Response.Body;
                string requestParam = string.Empty;
                string responseParam = string.Empty;
                try
                {
                    (context.RequestServices.GetService(typeof(RequestLogModel)) as RequestLogModel).RequestDate = DateTime.Now;
                    try
                    {
                        // å­˜å‚¨è¯·æ±‚数据
                        requestParam = RequestDataLog(context);
                        context.Request.Body.Position = 0;
                    }
                    catch { }
                    using MemoryStream ms = new();
                    context.Response.Body = ms;
                    await _next(context);
                    try
                    {
                        // å­˜å‚¨å“åº”数据
                        responseParam = ResponseDataLog(context.Response);
                    }
                    catch { }
                    ms.Position = 0;
                    await ms.CopyToAsync(originalBody);
                    #region è¿‡æ»¤æŽ¥å£
                    string path = context.Request.Path.Value;
                    if (!string.IsNullOrEmpty(path))
                    {
                        List<string> RequestPaths = new List<string>();
                        string RequestPath = AppSettings.Configuration[nameof(RequestPath)];
                        if (!string.IsNullOrEmpty(RequestPath)) RequestPaths = RequestPath.Split(',').ToList();
                        bool containsAny = RequestPaths.Any(k => path.Contains(k, StringComparison.OrdinalIgnoreCase));
                        if (!containsAny) Logger.Add(requestParam, responseParam);//记录系统日志
                        //记录接口日志
                        var ignoreKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "get" };
                        containsAny = RequestPaths.Where(k => !ignoreKeywords.Contains(k))
                           .Any(k => path.Contains(k, StringComparison.OrdinalIgnoreCase));
                        if (containsAny && context != null) LoggerNew.Add(requestParam, responseParam, context);
                    }
                    #endregion
                    //if (!(context.Request.Path.Value?.Contains("get") ?? true))
                    //    Logger.Add(requestParam, responseParam);
                }
                catch (Exception ex)
                {
                    // è®°å½•异常
                }
                finally
                {
                    context.Response.Body = originalBody;
                }
            }
            else
            {
                await _next(context);
            }
        }
        private string RequestDataLog(HttpContext context)
        {
            var request = context.Request;
            var sr = new StreamReader(request.Body);
            object obj = new
            {
                QueryString = request.QueryString.ToString(),
                BodyData = sr.ReadToEndAsync().Result
            };
            string data = JsonConvert.SerializeObject(obj);
            request.Body.Position = 0;
            return data;
        }
        private string ResponseDataLog(HttpResponse response)
        {
            response.Body.Position = 0;
            using StreamReader stream = new StreamReader(response.Body, leaveOpen: true);
            string body = stream.ReadToEnd();
            response.Body.Position = 0;
            return body;
        }
    }
}
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/LogCleanupService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,426 @@
using Microsoft.Extensions.Hosting;
using SqlSugar;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using WIDESEAWCS_Core.DB;
using WIDESEAWCS_Core.Seed;
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_Core.LogHelper
{
    /// <summary>
    /// æ—¥å¿—数据清理服务
    /// </summary>
    public class LogCleanupService : IHostedService, IDisposable
    {
        private Timer _cleanupTimer;
        private bool _isDisposed = false;
        private readonly object _lockObject = new object();
        /// <summary>
        /// æ•°æ®ä¿ç•™å¤©æ•°ï¼ˆé»˜è®¤90天)
        /// </summary>
        public int RetentionDays { get; set; } = 90;
        /// <summary>
        /// æ¸…理间隔(小时,默认24小时)
        /// </summary>
        public int CleanupIntervalHours { get; set; } = 24;
        /// <summary>
        /// æ¸…理执行时间(小时,0-23,默认凌晨2点)
        /// </summary>
        public int CleanupHour { get; set; } = 2;
        /// <summary>
        /// æ˜¯å¦å¯ç”¨å½’æ¡£
        /// </summary>
        public bool EnableArchive { get; set; } = false;
        /// <summary>
        /// æ˜¯å¦å¯ç”¨è‡ªåŠ¨æ¸…ç†
        /// </summary>
        public bool EnableAutoCleanup { get; set; } = true;
        /// <summary>
        /// æ—¥å¿—文件保留天数
        /// </summary>
        public int LogRetentionDays { get; set; } = 30;
        /// <summary>
        /// æ—¥å¿—文件路径
        /// </summary>
        private string LogFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "LogCleanup", $"Cleanup_{DateTime.Now:yyyyMMdd}.log");
        public Task StartAsync(CancellationToken cancellationToken)
        {
            if (EnableAutoCleanup)
            {
                // ç¡®ä¿æ—¥å¿—目录存在
                EnsureLogDirectoryExists();
                // è®¡ç®—到下一个清理时间的间隔
                var now = DateTime.Now;
                var nextCleanupTime = DateTime.Today.AddHours(CleanupHour);
                if (now > nextCleanupTime)
                {
                    nextCleanupTime = nextCleanupTime.AddDays(1);
                }
                var timeToFirstCleanup = nextCleanupTime - now;
                // è®¾ç½®å®šæ—¶å™¨ï¼Œåœ¨æŒ‡å®šæ—¶é—´æ‰§è¡Œæ¸…理
                _cleanupTimer = new Timer(CleanupCallback, null,
                    timeToFirstCleanup,
                    TimeSpan.FromHours(CleanupIntervalHours));
                WriteLog($"日志清理服务已启动,首次清理时间: {nextCleanupTime:yyyy-MM-dd HH:mm:ss}");
            }
            return Task.CompletedTask;
        }
        /// <summary>
        /// ç¡®ä¿æ—¥å¿—目录存在
        /// </summary>
        private void EnsureLogDirectoryExists()
        {
            try
            {
                string logDirectory = Path.GetDirectoryName(LogFilePath);
                if (!Directory.Exists(logDirectory))
                {
                    Directory.CreateDirectory(logDirectory);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"创建日志目录失败: {ex.Message}");
            }
        }
        /// <summary>
        /// å†™å…¥æ—¥å¿—到文件
        /// </summary>
        private void WriteLog(string message, bool isError = false)
        {
            try
            {
                lock (_lockObject)
                {
                    EnsureLogDirectoryExists();
                    string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {(isError ? "[ERROR]" : "[INFO]")} {message}";
                    // å†™å…¥æ–‡ä»¶
                    File.AppendAllText(LogFilePath, logMessage + Environment.NewLine);
                    // åŒæ—¶è¾“出到控制台(可选,便于调试)
                    Console.WriteLine(logMessage);
                    // æ—¥å¿—文件过大时自动清理(保留最近30天的日志文件)
                    AutoCleanupLogFiles();
                }
            }
            catch (Exception ex)
            {
                // å¦‚果文件日志失败,至少输出到控制台
                Console.WriteLine($"写入日志文件失败: {ex.Message}");
                Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
            }
        }
        /// <summary>
        /// è‡ªåŠ¨æ¸…ç†è¿‡æœŸçš„æ—¥å¿—æ–‡ä»¶ï¼ˆä¿ç•™30天)
        /// </summary>
        private void AutoCleanupLogFiles()
        {
            try
            {
                string logDirectory = Path.GetDirectoryName(LogFilePath);
                if (Directory.Exists(logDirectory))
                {
                    var files = Directory.GetFiles(logDirectory, "Cleanup_*.log");
                    DateTime cutoffDate = DateTime.Now.AddDays(-30);
                    foreach (var file in files)
                    {
                        try
                        {
                            // ä»Žæ–‡ä»¶åä¸­æå–日期
                            string fileName = Path.GetFileNameWithoutExtension(file);
                            if (fileName.StartsWith("Cleanup_"))
                            {
                                string dateStr = fileName.Replace("Cleanup_", "");
                                if (DateTime.TryParseExact(dateStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out DateTime fileDate))
                                {
                                    if (fileDate < cutoffDate)
                                    {
                                        File.Delete(file);
                                        WriteLog($"删除过期日志文件: {Path.GetFileName(file)}");
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"删除过期日志文件失败 {file}: {ex.Message}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"清理日志文件失败: {ex.Message}");
            }
        }
        private void CleanupCallback(object state)
        {
            Task.Run(() => CleanupAsync());
        }
        /// <summary>
        /// æ‰§è¡Œæ¸…理
        /// </summary>
        public async Task CleanupAsync()
        {
            try
            {
                WriteLog($"开始清理 {RetentionDays} å¤©å‰çš„æ—¥å¿—数据");
                using (SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
                {
                    ConnectionString = DBContext.GetMainConnectionDb().Connection,
                    IsAutoCloseConnection = true,
                    DbType = DBContext.DbType,
                }))
                {
                    DateTime cutoffDate = DateTime.Now.AddDays(-RetentionDays);
                    WriteLog($"截止日期: {cutoffDate:yyyy-MM-dd HH:mm:ss}");
                    if (EnableArchive)
                    {
                        // å…ˆå½’档再删除
                        await ArchiveDataAsync(sugarClient, cutoffDate);
                    }
                    // åˆ é™¤åŽ†å²æ•°æ®
                    int deletedCount = await sugarClient.Deleteable<Dt_InterfaceLog>()
                        .Where(x => x.CreateDate < cutoffDate)
                        .ExecuteCommandAsync();
                    WriteLog($"清理完成,共删除 {deletedCount} æ¡è®°å½•");
                    // å¯é€‰ï¼šè®°å½•清理日志到数据库
                    await LogCleanupResult(sugarClient, deletedCount, cutoffDate);
                }
            }
            catch (Exception ex)
            {
                WriteLog($"清理失败: {ex.Message}", true);
                WriteLog($"详细错误: {ex.ToString()}", true);
            }
        }
        private async Task ArchiveDataAsync(SqlSugarClient sugarClient, DateTime cutoffDate)
        {
            try
            {
                string archiveTableName = $"Dt_InterfaceLog_Archive_{DateTime.Now:yyyyMM}";
                WriteLog($"开始归档数据到表: {archiveTableName}");
                // åˆ›å»ºå½’档表
                string createTableSql = $@"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='{archiveTableName}' AND xtype='U')
                    BEGIN
                        SELECT * INTO {archiveTableName} FROM Dt_InterfaceLog WHERE 1=0
                        ALTER TABLE {archiveTableName} ADD ArchiveDate datetime DEFAULT GETDATE()
                    END";
                await sugarClient.Ado.ExecuteCommandAsync(createTableSql);
                // å½’档数据
                string archiveSql = $@"
                    INSERT INTO {archiveTableName} (Id, ApiCode, ApiName, ApiAddress, PushFrequency,
                        PushState, Requestor, RequestParameters, RequestParametersHash, Recipient,
                        ResponseParameters, ResponseParametersHash, Remark, CreateTime, ArchiveDate)
                    SELECT Id, ApiCode, ApiName, ApiAddress, PushFrequency, PushState,
                        Requestor, RequestParameters, RequestParametersHash, Recipient,
                        ResponseParameters, ResponseParametersHash, Remark, CreateTime, GETDATE()
                    FROM Dt_InterfaceLog
                    WHERE CreateTime < @CutoffDate";
                int archivedCount = await sugarClient.Ado.ExecuteCommandAsync(archiveSql,
                    new { CutoffDate = cutoffDate });
                WriteLog($"归档完成,共归档 {archivedCount} æ¡è®°å½•到 {archiveTableName}");
            }
            catch (Exception ex)
            {
                WriteLog($"归档失败: {ex.Message}", true);
            }
        }
        private async Task LogCleanupResult(SqlSugarClient sugarClient, int deletedCount, DateTime cutoffDate)
        {
            try
            {
                // åˆ›å»ºæ¸…理日志表(如果不存在)
                string createLogTableSql = @"
                    IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='LogCleanupHistory' AND xtype='U')
                    BEGIN
                        CREATE TABLE LogCleanupHistory (
                            Id INT IDENTITY(1,1) PRIMARY KEY,
                            TableName NVARCHAR(100),
                            DeletedCount INT,
                            CutoffDate DATETIME,
                            CleanupTime DATETIME DEFAULT GETDATE()
                        )
                    END";
                await sugarClient.Ado.ExecuteCommandAsync(createLogTableSql);
                // è®°å½•清理日志
                string insertLogSql = @"
                    INSERT INTO LogCleanupHistory (TableName, DeletedCount, CutoffDate, CleanupTime)
                    VALUES ('Dt_InterfaceLog', @DeletedCount, @CutoffDate, GETDATE())";
                await sugarClient.Ado.ExecuteCommandAsync(insertLogSql,
                    new { DeletedCount = deletedCount, CutoffDate = cutoffDate });
                WriteLog($"清理记录已写入数据库日志表");
            }
            catch (Exception ex)
            {
                WriteLog($"写入数据库日志失败: {ex.Message}", true);
            }
        }
        /// <summary>
        /// æ‰‹åŠ¨æ‰§è¡Œæ¸…ç†
        /// </summary>
        public async Task<int> ManualCleanup(int days = 90)
        {
            try
            {
                WriteLog($"手动清理开始,删除超过 {days} å¤©çš„æ•°æ®");
                using (SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
                {
                    ConnectionString = DBContext.GetMainConnectionDb().Connection,
                    IsAutoCloseConnection = true,
                    DbType = DBContext.DbType,
                }))
                {
                    DateTime cutoffDate = DateTime.Now.AddDays(-days);
                    int deletedCount = await sugarClient.Deleteable<Dt_InterfaceLog>()
                        .Where(x => x.CreateDate < cutoffDate)
                        .ExecuteCommandAsync();
                    WriteLog($"手动清理完成,删除 {deletedCount} æ¡è®°å½•");
                    return deletedCount;
                }
            }
            catch (Exception ex)
            {
                WriteLog($"手动清理失败: {ex.Message}", true);
                return 0;
            }
        }
        /// <summary>
        /// èŽ·å–æ¸…ç†ç»Ÿè®¡ä¿¡æ¯
        /// </summary>
        public async Task<CleanupStatistics> GetStatistics()
        {
            try
            {
                using (SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
                {
                    ConnectionString = DBContext.GetMainConnectionDb().Connection,
                    IsAutoCloseConnection = true,
                    DbType = DBContext.DbType,
                }))
                {
                    var totalCount = await sugarClient.Queryable<Dt_InterfaceLog>().CountAsync();
                    var oldestLog = await sugarClient.Queryable<Dt_InterfaceLog>()
                        .OrderBy(x => x.CreateDate)
                        .FirstAsync();
                    var newestLog = await sugarClient.Queryable<Dt_InterfaceLog>()
                        .OrderBy(x => x.CreateDate, OrderByType.Desc)
                        .FirstAsync();
                    return new CleanupStatistics
                    {
                        TotalCount = totalCount,
                        OldestLogTime = oldestLog?.CreateDate,
                        NewestLogTime = newestLog?.CreateDate,
                        RetentionDays = RetentionDays,
                        LastCleanupTime = GetLastCleanupTime(),
                        LogFilePath = LogFilePath
                    };
                }
            }
            catch (Exception ex)
            {
                WriteLog($"获取统计信息失败: {ex.Message}", true);
                return null;
            }
        }
        private DateTime? GetLastCleanupTime()
        {
            try
            {
                string logDirectory = Path.GetDirectoryName(LogFilePath);
                if (Directory.Exists(logDirectory))
                {
                    var latestLogFile = Directory.GetFiles(logDirectory, "Cleanup_*.log")
                        .OrderByDescending(f => f)
                        .FirstOrDefault();
                    if (latestLogFile != null && File.Exists(latestLogFile))
                    {
                        return File.GetLastWriteTime(latestLogFile);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"获取上次清理时间失败: {ex.Message}");
            }
            return null;
        }
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _cleanupTimer?.Dispose();
            WriteLog("日志清理服务已停止");
            return Task.CompletedTask;
        }
        public void Dispose()
        {
            if (!_isDisposed)
            {
                _cleanupTimer?.Dispose();
                _isDisposed = true;
            }
        }
    }
    /// <summary>
    /// æ¸…理统计信息
    /// </summary>
    public class CleanupStatistics
    {
        public int TotalCount { get; set; }
        public DateTime? OldestLogTime { get; set; }
        public DateTime? NewestLogTime { get; set; }
        public int RetentionDays { get; set; }
        public DateTime? LastCleanupTime { get; set; }
        public string LogFilePath { get; set; }
    }
}
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/LoggerNew.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,431 @@
using SqlSugar;
using System.Collections.Concurrent;
using System.Text;
using WIDESEAWCS_Core.HttpContextUser;
using WIDESEAWCS_Core.Seed;
using WIDESEAWCS_Model.Models;
using System.Security.Cryptography;
using WIDESEAWCS_Core.Helper;
using Newtonsoft.Json.Linq;
using WIDESEAWCS_DTO.RGV.FOURBOT;
using WIDESEAWCS_DTO.WMS;
using WIDESEAWCS_DTO;
namespace WIDESEAWCS_Core.LogHelper
{
    public static class LoggerNew
    {
        // ç”¨äºŽç¼“存当天已存在的日志(key: æ—¥æœŸ+ApiCode+请求哈希+响应哈希)
        private static ConcurrentDictionary<string, Dt_InterfaceLog> _cacheLogs = new ConcurrentDictionary<string, Dt_InterfaceLog>();
        // é˜Ÿåˆ—用于存储需要新增或更新的日志
        public static ConcurrentQueue<Dt_InterfaceLog> loggerQueueData = new ConcurrentQueue<Dt_InterfaceLog>();
        // æ¯å¤©å‡Œæ™¨æ¸…空缓存
        private static DateTime _lastClearDate = DateTime.Now.Date;
        static LoggerNew()
        {
            Task.Run(() =>
            {
                StartWriteLog();
            });
            // å¯åŠ¨ä¸€ä¸ªåŽå°ä»»åŠ¡ï¼Œæ¯å¤©å‡Œæ™¨æ¸…ç©ºç¼“å­˜
            Task.Run(() =>
            {
                while (true)
                {
                    Thread.Sleep(60000); // æ¯åˆ†é’Ÿæ£€æŸ¥ä¸€æ¬¡
                    DateTime today = DateTime.Now.Date;
                    if (_lastClearDate < today)
                    {
                        _cacheLogs.Clear();
                        _lastClearDate = today;
                        Console.WriteLine($"Logger缓存已清空,日期:{today:yyyy-MM-dd}");
                    }
                }
            });
        }
        static void StartWriteLog()
        {
            List<Dt_InterfaceLog> batchList = new List<Dt_InterfaceLog>();
            while (true)
            {
                try
                {
                    if (loggerQueueData.Count() > 0 && batchList.Count < 500)
                    {
                        DequeueToBatchList(batchList);
                        continue;
                    }
                    Thread.Sleep(5000);
                    if (batchList.Count == 0) { continue; }
                    SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
                    {
                        ConnectionString = DBContext.GetMainConnectionDb().Connection,
                        IsAutoCloseConnection = true,
                        DbType = DBContext.DbType,
                    });
                    try
                    {
                        sugarClient.BeginTran(); // å¼€å¯äº‹åŠ¡
                        var toInsert = new List<Dt_InterfaceLog>();
                        var toUpdate = new List<Dt_InterfaceLog>();
                        foreach (var log in batchList)
                        {
                            // å¦‚果已经有 Id æˆ–者 IsExisting æ ‡å¿—,直接处理
                            if (log.Id > 0)
                            {
                                toUpdate.Add(log);
                                continue;
                            }
                            // æ ¹æ®å“ˆå¸Œå€¼æŸ¥è¯¢æ˜¯å¦å·²å­˜åœ¨
                            var existing = sugarClient.Queryable<Dt_InterfaceLog>()
                                .First(x => x.RequestParametersHash == log.RequestParametersHash
                                         && x.ResponseParametersHash == log.ResponseParametersHash
                                         && x.ApiCode == log.ApiCode
                                         && SqlFunc.DateIsSame(x.CreateDate, DateTime.Now)); // åŒä¸€å¤©
                            if (existing != null)
                            {
                                // å·²å­˜åœ¨ï¼Œæ›´æ–°
                                log.Id = existing.Id;
                                log.PushFrequency = existing.PushFrequency + 1; // å åŠ æ¬¡æ•°
                                log.ModifyDate = DateTime.Now;
                                toUpdate.Add(log);
                                // æ›´æ–°ç¼“存中的 Id
                                string cacheKey = GenerateCacheKey(log.ApiCode, log.RequestParametersHash, log.ResponseParametersHash);
                                if (_cacheLogs.TryGetValue(cacheKey, out var cachedLog))
                                {
                                    cachedLog.Id = existing.Id;
                                }
                            }
                            else
                            {
                                // ä¸å­˜åœ¨ï¼Œæ–°å¢ž
                                toInsert.Add(log);
                            }
                        }
                        // æ‰¹é‡æ–°å¢ž
                        if (toInsert.Any())
                        {
                            int rows = sugarClient.Fastest<Dt_InterfaceLog>().BulkCopy(toInsert);
                            // èŽ·å–æ–°å¢žæ—¥å¿—çš„è‡ªå¢žID并更新缓存
                            foreach (var insertedLog in toInsert)
                            {
                                var savedLog = sugarClient.Queryable<Dt_InterfaceLog>()
                                    .First(x => x.RequestParametersHash == insertedLog.RequestParametersHash
                                             && x.ResponseParametersHash == insertedLog.ResponseParametersHash);
                                if (savedLog != null)
                                {
                                    string cacheKey = GenerateCacheKey(insertedLog.ApiCode,
                                        insertedLog.RequestParametersHash, insertedLog.ResponseParametersHash);
                                    if (_cacheLogs.TryGetValue(cacheKey, out var cachedLog))
                                    {
                                        cachedLog.Id = savedLog.Id;
                                    }
                                }
                            }
                        }
                        // æ‰¹é‡æ›´æ–°æŽ¨é€æ¬¡æ•°
                        if (toUpdate.Any())
                        {
                            foreach (var log in toUpdate)
                            {
                                sugarClient.Updateable<Dt_InterfaceLog>()
                                    .SetColumns(it => new Dt_InterfaceLog
                                    {
                                        PushFrequency = log.PushFrequency,
                                        PushState = log.PushState,
                                        Remark = log.Remark,
                                        //CreateDate = log.CreateDate
                                        ModifyDate = log.ModifyDate
                                    })
                                    .Where(it => it.Id == log.Id)
                                    .ExecuteCommand();
                            }
                        }
                        sugarClient.CommitTran(); // æäº¤äº‹åŠ¡
                    }
                    catch (Exception ex)
                    {
                        sugarClient.RollbackTran(); // å›žæ»šäº‹åŠ¡
                        Console.WriteLine($"处理日志批次失败: {ex.Message}");
                    }
                    finally
                    {
                        batchList.Clear();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
        private static void DequeueToBatchList(List<Dt_InterfaceLog> batchList)
        {
            if (loggerQueueData.TryDequeue(out Dt_InterfaceLog log) && log != null)
            {
                batchList.Add(log);
            }
        }
        /// <summary>
        /// è®¡ç®—字符串的SHA256哈希值
        /// </summary>
        private static string ComputeHash(string input)
        {
            if (string.IsNullOrEmpty(input))
                return string.Empty;
            using (SHA256 sha256 = SHA256.Create())
            {
                byte[] bytes = Encoding.UTF8.GetBytes(input);
                byte[] hashBytes = sha256.ComputeHash(bytes);
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
            }
        }
        /// <summary>
        /// ç”Ÿæˆç¼“å­˜Key
        /// </summary>
        private static string GenerateCacheKey(string apiCode, string requestHash, string responseHash)
        {
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            return $"{today}_{apiCode}_{requestHash}_{responseHash}";
        }
        /// <summary>
        /// èŽ·å–æˆ–åˆ›å»ºæ—¥å¿—ï¼ˆç”¨äºŽå åŠ æŽ¨é€æ¬¡æ•°ï¼‰
        /// </summary>
        /// <param name="apiCode">编号</param>
        /// <param name="apiName">名称</param>
        /// <param name="apiAddress">地址</param>
        /// <param name="requestParameters">请求内容</param>
        /// <param name="responseParameters">响应内容</param>
        /// <param name="pushState">状态</param>
        /// <param name="requestorUserIP">请求用户IP</param>
        /// <param name="requestor">请求方</param>
        /// <param name="recipient">接收方</param>
        /// <param name="remark">备注</param>
        /// <returns></returns>
        private static Dt_InterfaceLog GetOrCreateLog(string apiCode, string apiName, string apiAddress,
            string requestParameters, string responseParameters, int pushState,
            string requestorUserIP, string requestor, string recipient, string remark)
        {
            string requestHash = ComputeHash(requestParameters);
            string responseHash = ComputeHash(responseParameters);
            string cacheKey = GenerateCacheKey(apiCode, requestHash, responseHash);
            // å°è¯•从缓存中获取
            if (_cacheLogs.TryGetValue(cacheKey, out Dt_InterfaceLog existingLog))
            {
                // å­˜åœ¨ç›¸åŒå‚数的日志,增加推送次数
                existingLog.PushFrequency++;
                existingLog.PushState = pushState; // æ›´æ–°æŽ¨é€çŠ¶æ€
                existingLog.Remark = remark; // æ›´æ–°å¤‡æ³¨
                //existingLog.CreateDate = DateTime.Now; // æ›´æ–°æ—¶é—´
                existingLog.ModifyDate = DateTime.Now; // æ›´æ–°æ—¶é—´
                // è¿”回现有日志(需要更新数据库)
                return existingLog;
            }
            else
            {
                // ä¸å­˜åœ¨ï¼Œåˆ›å»ºæ–°æ—¥å¿—
                var newLog = new Dt_InterfaceLog
                {
                    ApiCode = apiCode,
                    ApiName = apiName,
                    ApiAddress = apiAddress,
                    PushFrequency = 1,
                    PushState = pushState,
                    RequestorUserIP = requestorUserIP,
                    Requestor = requestor,
                    RequestParameters = requestParameters,
                    RequestParametersHash = requestHash,
                    Recipient = recipient,
                    ResponseParameters = responseParameters,
                    ResponseParametersHash = responseHash,
                    Remark = remark,
                    Creater = "System",
                    CreateDate = DateTime.Now
                };
                // åŠ å…¥ç¼“å­˜
                _cacheLogs.TryAdd(cacheKey, newLog);
                return newLog;
            }
        }
        /// <summary>
        /// æ·»åŠ æŽ¥å£æ—¥å¿—ï¼ˆå¸¦URL、请求参数、响应参数、开始时间)
        /// </summary>
        public static void Add(string apiCode, string apiName, string apiAddress,
            string requestParameters, string responseParameters,
            int pushState = 0, string recipient = "", string remark = "")
        {
            Dt_InterfaceLog log = null;
            try
            {
                // èŽ·å–å½“å‰ç”¨æˆ·
                IUser? user = App.User;
                // èŽ·å–æˆ–åˆ›å»ºæ—¥å¿—ï¼ˆè‡ªåŠ¨å¤„ç†å åŠ ï¼‰
                log = GetOrCreateLog(apiCode, apiName, apiAddress,
                    requestParameters, responseParameters, pushState,
                    "", user?.UserName ?? "", recipient, remark);
            }
            catch (Exception exception)
            {
                // å¦‚果发生异常,创建基础日志对象
                log = log ?? new Dt_InterfaceLog
                {
                    ApiCode = apiCode,
                    ApiName = apiName,
                    ApiAddress = apiAddress,
                    PushFrequency = 1,
                    PushState = pushState,
                    RequestParameters = requestParameters,
                    ResponseParameters = responseParameters,
                    Creater = "System",
                    Remark = $"记录日志时发生异常:{exception.Message}"
                };
            }
            // æ·»åŠ ç³»ç»Ÿæ—¥å¿—
            loggerQueueData.Enqueue(log);
        }
        /// <summary>
        /// æ·»åŠ æŽ¥å£æ—¥å¿—ï¼ˆä»ŽHttpContext自动获取信息)
        /// </summary>
        /// <param name="requestParameters">请求内容</param>
        /// <param name="responseParameters">响应内容</param>
        /// <param name="apiCode">接口编号</param>
        /// <param name="apiName">接口名称</param>
        /// <param name="pushState">推送状态</param>
        /// <param name="recipient">接收方</param>
        /// <param name="remark">备注</param>
        public static void Add(string requestParameters, string responseParameters, HttpContext context, string apiCode = "", string apiName = "", int pushState = 0, string recipient = "WCS", string remark = "")
        {
            Dt_InterfaceLog log = null;
            try
            {
                // å¦‚果请求方法为OPTIONS,则返回
                if (context.Request.Method == "OPTIONS") return;
                #region æ•°æ®è½¬æ¢
                // è§£æžå¤–层 JSON
                JObject outerObj = JObject.Parse(requestParameters);
                // èŽ·å– BodyData çš„值(它是一个字符串)
                requestParameters = outerObj["BodyData"]?.ToString() ?? requestParameters;
                // æž„建完整的接口地址
                string apiAddress = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + context.Request.Path;
                // èŽ·å–å®¢æˆ·ç«¯IP
                string clientIP = GetClientIP(context)?.Replace("::ffff:", "") ?? "";
                string requestor = "";//请求方
                if (apiAddress.Contains("/api/WMS"))
                {
                    requestor = "WMS";//秒优
                    var wMSReturn = responseParameters.DeserializeObject<WMSReturn>();
                    pushState = wMSReturn.code == 0 ? 1 : 2;
                }
                else if (apiAddress.Contains("/api/KHAGV"))
                {
                    requestor = "海康AGV";
                    var hIKROBOTReturn = responseParameters.DeserializeObject<HIKROBOTReturn>();
                    pushState = hIKROBOTReturn.code == "SUCCESS" ? 1 : 2;
                }
                else if (apiAddress.Contains("/api/KLS"))
                {
                    requestor = "凯乐士AGV";
                    ReturnGALAXIS returnGALAXIS = responseParameters.DeserializeObject<ReturnGALAXIS>();
                    pushState = returnGALAXIS.returnStatus == 0 ? 1 : 2;
                }
                else if (apiAddress.Contains("/api/RGV"))
                {
                    requestor = "四向穿梭车";//旷视河图
                    FOURBOTReturn fOURBOTReturn = responseParameters.DeserializeObject<FOURBOTReturn>();
                    pushState = fOURBOTReturn.returnCode == 0 ? 1 : 2;
                }
                apiCode = apiAddress.Split("/").Last();
                apiName = AppSettings.Configuration.GetValue($"{requestor}:{apiCode}", apiCode);
                #endregion
                // èŽ·å–æˆ–åˆ›å»ºæ—¥å¿—ï¼ˆè‡ªåŠ¨å¤„ç†å åŠ ï¼‰
                log = GetOrCreateLog(apiCode, apiName, apiAddress,
                    requestParameters, responseParameters, pushState,
                    clientIP, requestor, recipient, remark);
            }
            catch (Exception exception)
            {
                // å¦‚果发生异常,创建基础日志对象
                log = log ?? new Dt_InterfaceLog
                {
                    ApiCode = apiCode,
                    ApiName = apiName,
                    PushFrequency = 1,
                    PushState = pushState,
                    RequestParameters = requestParameters,
                    ResponseParameters = responseParameters,
                    Creater = "System",
                    Remark = $"记录日志时发生异常:{exception.Message}"
                };
            }
            // æ·»åŠ ç³»ç»Ÿæ—¥å¿—
            loggerQueueData.Enqueue(log);
        }
        /// <summary>
        /// æ‰‹åŠ¨å¢žåŠ æŽ¨é€æ¬¡æ•°ï¼ˆç”¨äºŽé‡è¯•åœºæ™¯ï¼‰
        /// </summary>
        public static void IncrementPushCount(string apiCode, string requestParameters, string responseParameters)
        {
            string requestHash = ComputeHash(requestParameters);
            string responseHash = ComputeHash(responseParameters);
            string cacheKey = GenerateCacheKey(apiCode, requestHash, responseHash);
            if (_cacheLogs.TryGetValue(cacheKey, out Dt_InterfaceLog existingLog))
            {
                existingLog.PushFrequency++;
                loggerQueueData.Enqueue(existingLog);
            }
        }
        public static string GetClientIP(HttpContext context)
        {
            // èŽ·å–è¯·æ±‚å¤´ä¸­çš„X-Forwarded-For字段,并将其转换为字符串
            var ip = context.Request.Headers["X-Forwarded-For"].ObjToString();
            // å¦‚æžœX-Forwarded-For字段为空,则获取远程IP地址
            if (string.IsNullOrEmpty(ip))
            {
                ip = context.Connection.RemoteIpAddress.ObjToString();
            }
            // è¿”回IP地址
            return ip;
        }
    }
}
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Filter/Log/MiddlewareHelpersNew.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Builder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WIDESEAWCS_Core.Middlewares
{
    public static class MiddlewareHelpersNew
    {
        /// <summary>
        /// è¯·æ±‚响应中间件
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseApiLogMiddlewareNew(this IApplicationBuilder app)
        {
            return app.UseMiddleware<ApiLogMiddlewareNew>();
        }
    }
}
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Program.cs
@@ -18,6 +18,7 @@
using WIDESEAWCS_Core.Extensions;
using WIDESEAWCS_Core.Filter;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_Core.Middlewares;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.QuartzExtensions;
@@ -50,6 +51,25 @@
builder.Services.AddHostedService<SeedDataHostedService>();//初始化数据库
builder.Services.AddDbSetup();//Db å¯åŠ¨æœåŠ¡
#region æ·»åŠ æ—¥å¿—æ¸…ç†æœåŠ¡
var cleanupRetentionDays = builder.Configuration.GetValue<int>("LogCleanup:RetentionDays", 90);
var cleanupHour = builder.Configuration.GetValue<int>("LogCleanup:CleanupHour", 2);
var enableAutoCleanup = builder.Configuration.GetValue<bool>("LogCleanup:EnableAutoCleanup", true);
var enableArchive = builder.Configuration.GetValue<bool>("LogCleanup:EnableArchive", false);
var logRetentionDays = builder.Configuration.GetValue<int>("LogCleanup:LogRetentionDays", 30);
builder.Services.AddSingleton(sp => new LogCleanupService
{
    RetentionDays = cleanupRetentionDays,
    CleanupHour = cleanupHour,
    EnableAutoCleanup = enableAutoCleanup,
    EnableArchive = enableArchive,
    LogRetentionDays= logRetentionDays,
    CleanupIntervalHours = 24
});
builder.Services.AddHostedService(sp => sp.GetRequiredService<LogCleanupService>());
#endregion
//builder.Services.AddScoped<QuartzJobCreateDataTabel>();//任务调度 æ³¨å…¥åˆ›å»ºQuartzJob数据库表类
//builder.Services.AddHostedService<QuartzJobDataTableHostedService>();//任务调度 æ˜ å°„QuartzJob数据库表
@@ -123,7 +143,8 @@
app.UseSwaggerMiddle(() => Assembly.GetExecutingAssembly().GetManifestResourceStream("WIDESEAWCS_Server.index.html") ?? throw new Exception("未找到WIDESEAWCS_Server.index.html文件"));
app.UseIpLimitMiddle();
app.UseApiLogMiddleware();
//app.UseApiLogMiddleware();
app.UseApiLogMiddlewareNew();
app.UseCors(AppSettings.Get(new string[] { "Cors", "PolicyName" }));
@@ -148,4 +169,14 @@
app.MapControllers();
#region åº”用启动时立即清理一次
//Task.Run(async () =>
//{
//    // ç­‰å¾…应用完全启动
//    await Task.Delay(30000);
//    var cleanupService = app.Services.GetRequiredService<LogCleanupService>();
//    await cleanupService.ManualCleanup(90);
//});
#endregion
app.Run();
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/WIDESEAWCS_Server.csproj
@@ -76,8 +76,4 @@
        </Content>
    </ItemGroup>
    <ItemGroup>
      <Folder Include="Log\" />
    </ItemGroup>
</Project>
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -17,6 +17,7 @@
  //5.PostgreSQL
  "DBType": "SqlServer",
  //连接字符串
  //"ConnectionString": "Data Source=172.16.161.20;Initial Catalog=WIDESEAWCS_GP;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWCS_GP;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //跨域
  "Cors": {
@@ -26,15 +27,39 @@
    // æ³¨æ„ï¼Œhttp://127.0.0.1:1818 å’Œ http://localhost:1818 æ˜¯ä¸ä¸€æ ·çš„
    "IPs": "http://127.0.0.1:8080,http://localhost:8080"
  },
  "LogCleanup": { //接口日志清理配置
    "RetentionDays": 90, // æ•°æ®ä¿ç•™å¤©æ•°
    "CleanupHour": 2, // æ¸…理执行时间(小时,0-23)
    "EnableAutoCleanup": true, // æ˜¯å¦å¯ç”¨è‡ªåŠ¨æ¸…ç†
    "EnableArchive": true, // æ˜¯å¦å¯ç”¨å½’档(归档到历史表)
    "LogRetentionDays": 30 // æ—¥å¿—文件保留天数
  },
  "ApiLogIgnore": "", //记录日志时,忽略的API名称,多个用逗号分隔,配置的不记录到数据库中
  "ApiName": "WIDESEAWCS",
  "ExpMinutes": 120,
  "RequestPath": "GET,WMS,RGV,KHAGV,KLSAGV", //,
  ///接口说明:接口日志记录使用
  "WMS": {
    "ReceiveTask": "WMS下发任务",
    "ContainerRequest": "容器流动请求",
    "UpOrDownContainer": "平库货位上下架容器",
    "CancelWMSTask": "WMS取消任务",
    "IQCResult": "外检结果反馈",
    "UpdateArea": "修改库位分区",
    "UpdateTaskGrade": "WMS修改任务优先级",
    "QueryStation": "区域库位信息查询",
    "UpLocationStatus": "更新仓库库位状态"
  },
  "海康AGV": { "UpdateTaskStatus": "海康AGV任务状态更新" },
  "凯乐士AGV": { "UpdateTaskStatus": "凯乐士AGV任务状态更新" },
  "四向穿梭车": { "UpdateTaskStatus": "四向穿梭车任务状态更新" },
  ///接口说明
  "QuartzJobAutoStart": true,
  "DBSeedEnable": false,
  "QuartzDBSeedEnable": false,
  "LogDeubgEnable": true, //是否记录调试日志
  "PrintSql": true, //打印SQL语句
  "LogAOPEnable": true, //是否记录AOP日志
  "LogDeubgEnable": false, //是否记录调试日志
  "PrintSql": false, //打印SQL语句
  "LogAOPEnable": false, //是否记录AOP日志
  "WebSocketEnable": true, //是否开启WebSocket服务
  "WebSocketPort": 9296 //WebSocket服务端口
}
´úÂë¹ÜÀí/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/Task/RGVTaskExtend.cs
@@ -528,6 +528,13 @@
            try
            {
                List<Dt_RGVLocationInfo> rGVLocationInfos = _rGVLocationInfoService.Repository.QueryData(x => blockPods.Contains(x.PalletCode));
                var palletCodes = rGVLocationInfos.Select(x => x.PalletCode).ToList();
                // èŽ·å– blockPods ä¸­å­˜åœ¨ä½† palletCodes ä¸­ä¸å­˜åœ¨çš„æ‰˜ç›˜ç 
                var missingPalletCodes = blockPods.Except(palletCodes).ToList();
                if (missingPalletCodes.Any())
                {
                    throw new Exception($"未找到以下阻碍托货位信息:{string.Join(",", missingPalletCodes)}");
                }
                var Items = _rGVLocationInfoService.GetMoveFreeLocationInfo(rGVLocationInfos);
                List<Dt_Task> tasks = Items.Item1;