Kaynağa Gözat

重构操作日志中间件,提升健壮性与性能

重构 RecordAPIOperationMiddleware,实现异步日志写入,优化依赖注入与参数解析,支持多种 userId 字段识别,增强设备与 IP 信息提取,日志字段长度控制,异常降级写入本地文件。移除部分异常抛出逻辑,删除 CustomException.cs,整体提升系统稳定性与可维护性。
Lyyyi 7 ay önce
ebeveyn
işleme
acc59dd4d5

+ 0 - 2
OASystem/OASystem.Api/Controllers/AuthController.cs

@@ -137,7 +137,6 @@ namespace OASystem.API.Controllers
                 AnnouncementUnReadCount = announcementUnReadCount
             };
 
-
             DateTime createZebraTime = DateTime.Now;
             string authorId = dto.Number + "Token";
             string authorToken = await RedisRepository.RedisFactory.CreateRedisRepository().StringGetAsync<string>(authorId);//string 取
@@ -237,7 +236,6 @@ namespace OASystem.API.Controllers
                 AnnouncementUnReadCount = announcementUnReadCount
             };
 
-
             DateTime createZebraTime = DateTime.Now;
             string authorId = dto.Number + "Token";
             string authorToken = await RedisRepository.RedisFactory.CreateRedisRepository().StringGetAsync<string>(authorId);//string 取

+ 2 - 4
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -16525,7 +16525,6 @@ FROM
         /// <param name="diId"></param>
         /// <param name="currUserId"></param>
         /// <returns></returns>
-        /// <exception cref="CustomException"></exception>
         private async Task<int> EnterExitCostVerifyIsNull(int diId, int currUserId)
         {
             var parentId = 0;
@@ -16540,7 +16539,7 @@ FROM
             {
                 var currencys = await EnterExitCostMobileGetCurrencyInit();
                 int addId = await _sqlSugar.Insertable(new Grp_EnterExitCost() { DiId = diId, CreateUserId = currUserId, CurrencyRemark = JsonConvert.SerializeObject(currencys) }).ExecuteReturnIdentityAsync();
-                if (addId < 1) throw new CustomException("出入境费用添加失败!");
+                
                 parentId = addId;
             }
 
@@ -18246,7 +18245,6 @@ FROM
         /// <param name="draftId"></param>
         /// <param name="currUserId"></param>
         /// <returns></returns>
-        /// <exception cref="CustomException"></exception>
         private async Task<int> EnterExitCostDraftVerifyIsNull(int draftId, int currUserId)
         {
             var parentId = 0;
@@ -18257,7 +18255,7 @@ FROM
             {
                 var currencys = await EnterExitCostDraftMobileGetCurrencyInit();
                 int addId = await _sqlSugar.Insertable(new Grp_EnterExitCostDraft() { CreateUserId = currUserId, CurrencyRemark = JsonConvert.SerializeObject(currencys) }).ExecuteReturnIdentityAsync();
-                if (addId < 1) throw new CustomException("出入境费用添加失败!");
+                
                 parentId = addId;
             }
 

+ 0 - 12
OASystem/OASystem.Api/Middlewares/CustomException.cs

@@ -1,12 +0,0 @@
-namespace OASystem.API.Middlewares
-{
-    /// <summary>
-    /// 自定义异常
-    /// </summary>
-    public class CustomException : Exception
-    {
-        public CustomException(string message) : base(message)
-        {
-        }
-    }
-}

+ 430 - 189
OASystem/OASystem.Api/Middlewares/RecordAPIOperationMiddleware.cs

@@ -3,6 +3,11 @@ using OASystem.Domain.Attributes;
 using OASystem.Domain.Entities.Customer;
 using UAParser;
 using static OASystem.API.OAMethodLib.JWTHelper;
+using System.Text;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
 
 namespace OASystem.API.Middlewares
 {
@@ -14,25 +19,44 @@ namespace OASystem.API.Middlewares
         private readonly RequestDelegate _next;
         private readonly HttpClient _httpClient;
         private readonly IConfiguration _config;
-        private readonly SqlSugarClient _sqlSugar;
+        private readonly ILogger<RecordAPIOperationMiddleware> _logger;
 
         /// <summary>
-        /// 初始化DeleteUserId
+        /// 初始化
         /// </summary>
         /// <param name="next"></param>
         /// <param name="config"></param>
-        /// <param name="httpClient"></param>
-        /// <param name="sqlSugar"></param>
-        public RecordAPIOperationMiddleware(RequestDelegate next, IConfiguration config, HttpClient httpClient, SqlSugarClient sqlSugar)
+        /// <param name="httpClientFactory"></param>
+        /// <param name="logger"></param>
+        public RecordAPIOperationMiddleware(
+            RequestDelegate next,
+            IConfiguration config,
+            IHttpClientFactory httpClientFactory,
+            ILogger<RecordAPIOperationMiddleware> logger)
         {
             _next = next;
-            _httpClient = httpClient;
             _config = config;
-            _sqlSugar = sqlSugar;
+            _logger = logger;
+            _httpClient = httpClientFactory.CreateClient();
+            _httpClient.Timeout = TimeSpan.FromSeconds(5);
         }
 
-        public async Task InvokeAsync(HttpContext context)
+        public async Task InvokeAsync(HttpContext context, IServiceProvider serviceProvider)
         {
+            // 跳过 OPTIONS 请求(CORS 预检请求)
+            if (context.Request.Method.Equals(HttpMethods.Options, StringComparison.OrdinalIgnoreCase))
+            {
+                await _next(context);
+                return;
+            }
+
+            // 跳过静态文件请求
+            if (IsStaticFileRequest(context.Request.Path))
+            {
+                await _next(context);
+                return;
+            }
+
             // 启用请求体流的缓冲,允许多次读取
             context.Request.EnableBuffering();
 
@@ -47,80 +71,99 @@ namespace OASystem.API.Middlewares
                 var startTime = DateTime.UtcNow;
                 int portType = 1, userId = 0, id = 0, status = 0;
                 string updatePreData = string.Empty, updateBefData = string.Empty;
-                bool param5Bool = false, param6Bool = false;
-                
+
                 try
                 {
                     userId = await ReadToken(context);
 
                     if (!string.IsNullOrEmpty(requestBodyText))
                     {
-                        object param1 = string.Empty, param2 = string.Empty, param3 = string.Empty, param4 = string.Empty,
-                               param5 = string.Empty, param6 = string.Empty, param7 = string.Empty, param8 = string.Empty;
                         var requestBodyJson = JsonConvert.DeserializeObject<Dictionary<string, object>>(requestBodyText);
-                        bool exists1 = requestBodyJson.TryGetValue("portType", out param1);
-                        bool exists2 = requestBodyJson.TryGetValue("userId", out param2);
-                        bool exists3 = requestBodyJson.TryGetValue("currUserId", out param3);
-                        bool exists4 = requestBodyJson.TryGetValue("createUserId", out param4);
-                        bool exists5 = requestBodyJson.TryGetValue("id", out param5);
-                        bool exists6 = requestBodyJson.TryGetValue("status", out param6);
-                        bool exists7 = requestBodyJson.TryGetValue("operationUserId", out param7);
-                        bool exists8 = requestBodyJson.TryGetValue("deleteUserId", out param8);
-
-                        if (exists1) int.TryParse(param1.ToString(), out portType);
-
-                        //用户Id处理
-                        if (userId < 1)
+
+                        if (requestBodyJson != null)
                         {
-                            if (apiLogAttribute.OperationEnum == OperationEnum.Login)
+                            // 提取参数
+                            if (requestBodyJson.TryGetValue("portType", out var param1Obj) && param1Obj != null)
                             {
-                                var number = requestBodyJson?["number"].ToString();
-                                if (!string.IsNullOrEmpty(number))
-                                {
-                                    var info = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Number.Equals(number)).FirstAsync();
+                                int.TryParse(param1Obj.ToString(), out portType);
+                            }
 
-                                    userId = info?.Id ?? 0;
-                                }
+                            if (requestBodyJson.TryGetValue("id", out var param5Obj) && param5Obj != null)
+                            {
+                                int.TryParse(param5Obj.ToString(), out id);
                             }
-                            else
+
+                            if (requestBodyJson.TryGetValue("status", out var param6Obj) && param6Obj != null)
                             {
-                                if (exists2) int.TryParse(param2.ToString(), out userId);
-                                else if (exists3) int.TryParse(param3.ToString(), out userId);
-                                else if (exists4) int.TryParse(param4.ToString(), out userId);
-                                else if (exists7) int.TryParse(param7.ToString(), out userId);
-                                else if (exists8) int.TryParse(param8.ToString(), out userId);
+                                int.TryParse(param6Obj.ToString(), out status);
                             }
-                        }
-                        //编辑前数据查询;
-                        if (exists5) param5Bool = int.TryParse(param5.ToString(), out id);
-                        if (exists6) param6Bool = int.TryParse(param6.ToString(), out status);
 
-                        if (param6Bool)
-                        {
-                            //  2 修改 
-                            if (status == 1) apiLogAttribute.OperationEnum = OperationEnum.Add;
-                            else if (status == 2)
+                            // 用户Id处理
+                            if (userId < 1)
                             {
-                                apiLogAttribute.OperationEnum = OperationEnum.Edit;
-                                updatePreData = await TableInfoToJson(apiLogAttribute.TableName, id);
+                                if (apiLogAttribute.OperationEnum == OperationEnum.Login)
+                                {
+                                    var number = requestBodyJson.TryGetValue("number", out var numberObj) ? numberObj?.ToString() : null;
+                                    if (!string.IsNullOrEmpty(number))
+                                    {
+                                        // 创建独立的数据库作用域
+                                        using var scope = serviceProvider.CreateScope();
+                                        var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+
+                                        var info = await sqlSugar.Queryable<Sys_Users>()
+                                            .Where(x => x.IsDel == 0 && x.Number.Equals(number))
+                                            .FirstAsync();
+
+                                        userId = info?.Id ?? 0;
+                                    }
+                                }
+                                else
+                                {
+                                    userId = ParseUserIdFromParams(requestBodyJson);
+                                }
                             }
-                        }
-                        else if (param5Bool)
-                        {
-                            if (apiLogAttribute.OperationEnum != OperationEnum.Del)
+
+                            // 根据status判断操作类型
+                            if (status > 0)
                             {
-                                //  id > 0 修改 
-                                if (id < 1) apiLogAttribute.OperationEnum = OperationEnum.Add;
-                                else if (id > 0)
+                                if (status == 1)
+                                    apiLogAttribute.OperationEnum = OperationEnum.Add;
+                                else if (status == 2)
                                 {
                                     apiLogAttribute.OperationEnum = OperationEnum.Edit;
-                                    updatePreData = await TableInfoToJson(apiLogAttribute.TableName, id);
+                                    if (id > 0)
+                                    {
+                                        // 使用独立的数据库连接获取修改前数据
+                                        using var scope = serviceProvider.CreateScope();
+                                        var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+                                        updatePreData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
+                                    }
                                 }
                             }
+                            // 根据id判断操作类型
+                            else if (id > 0 && apiLogAttribute.OperationEnum != OperationEnum.Del)
+                            {
+                                apiLogAttribute.OperationEnum = OperationEnum.Edit;
+                                // 使用独立的数据库连接获取修改前数据
+                                using var scope = serviceProvider.CreateScope();
+                                var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+                                updatePreData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
+                            }
+                            else if (apiLogAttribute.OperationEnum != OperationEnum.Del && id < 1)
+                            {
+                                apiLogAttribute.OperationEnum = OperationEnum.Add;
+                            }
                         }
                     }
                 }
-                catch (JsonException) { }
+                catch (JsonException)
+                {
+                    _logger.LogDebug("JSON解析失败,可能请求体不是JSON格式");
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "解析请求参数时发生错误");
+                }
 
                 // 保存原始响应体流
                 var originalResponseBody = context.Response.Body;
@@ -141,215 +184,413 @@ namespace OASystem.API.Middlewares
                 // 将响应体内容写回原始响应体流
                 await responseMemoryStream.CopyToAsync(originalResponseBody);
 
-                //修改后数据查询
-                if (param6Bool)
+                // 修改后数据查询
+                if (status == 2 && id > 0)
                 {
-                    //  2 修改 
-                    if (status == 2) updateBefData = await TableInfoToJson(apiLogAttribute.TableName, id);
+                    using var scope = serviceProvider.CreateScope();
+                    var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+                    updateBefData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
                 }
-                else if (param5Bool)
+                else if (apiLogAttribute.OperationEnum == OperationEnum.Edit && id > 0)
                 {
-
-                    if (apiLogAttribute.OperationEnum != OperationEnum.Del)
-                    {
-                        //  id > 0 修改 
-                        if (id > 0) updateBefData = await TableInfoToJson(apiLogAttribute.TableName, id);
-                    }
+                    using var scope = serviceProvider.CreateScope();
+                    var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+                    updateBefData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
                 }
-                string remoteIp = string.Empty,
-                       location = string.Empty;
 
-                // 检查请求头中的X-Forwarded-For,以获取真实的客户端IP地址
-                if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
-                {
-                    remoteIp = context.Request.Headers["X-Forwarded-For"].ToString().Split(',', StringSplitOptions.RemoveEmptyEntries)[0];
-                    //_logger.LogInformation($"IP:{remoteIp}");
-                }
-                else
+                // 异步记录日志,不阻塞响应
+                _ = Task.Run(async () =>
                 {
-                    remoteIp = context.Connection.RemoteIpAddress?.ToString();
-                }
+                    try
+                    {
+                        using var logScope = serviceProvider.CreateScope();
+                        var logSqlSugar = logScope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+
+                        await LogOperationAsync(
+                            logSqlSugar,
+                            context,
+                            apiLogAttribute,
+                            requestBodyText,
+                            responseBodyText,
+                            startTime,
+                            portType,
+                            userId,
+                            id,
+                            status,
+                            updatePreData,
+                            updateBefData
+                        );
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "异步记录操作日志失败");
+                    }
+                });
+            }
+            else
+            {
+                await _next(context);
+            }
+        }
 
-                (remoteIp, location) = await GetIpInfo(remoteIp);
-                //remoteIp = await GetExternalIp();
-                //location = await GetIpLocation(remoteIp);
+        /// <summary>
+        /// 从请求参数中解析用户ID
+        /// </summary>
+        private int ParseUserIdFromParams(Dictionary<string, object> requestBodyJson)
+        {
+            var userIdKeys = new[] { "userId", "currUserId", "createUserId", "operationUserId", "deleteUserId" };
 
-                string deviceType = string.Empty, browser = string.Empty, os = string.Empty;
-                var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault();
-                if (!string.IsNullOrEmpty(userAgent))
+            foreach (var key in userIdKeys)
+            {
+                if (requestBodyJson.TryGetValue(key, out var value) && value != null)
                 {
-                    // 解析User-Agent头
-                    var parser = Parser.GetDefault();
-                    var client = parser.Parse(userAgent);
-
-                    // 提取浏览器信息
-                    browser = client.UA.Family; // 浏览器名称
-                    var browserVersion = client.UA.Major + "." + client.UA.Minor + "." + client.UA.Patch; // 浏览器版本
-                    browser += $"({browserVersion})";
-
-                    // 提取操作系统信息
-                    os = client.OS.Family; // 操作系统名称
+                    if (int.TryParse(value.ToString(), out var parsedUserId) && parsedUserId > 0)
+                    {
+                        return parsedUserId;
+                    }
+                }
+            }
 
-                    var osVersion = string.Empty; // 操作系统版本
-                    if (!string.IsNullOrEmpty(client.OS.Major)) osVersion += client.OS.Major;
-                    if (!string.IsNullOrEmpty(client.OS.Minor)) osVersion += "." + client.OS.Minor;
-                    if (!string.IsNullOrEmpty(client.OS.Patch)) osVersion += "." + client.OS.Patch;
+            return 0;
+        }
 
-                    if (!string.IsNullOrEmpty(osVersion)) os += $"({osVersion})";
+        /// <summary>
+        /// 异步记录操作日志
+        /// </summary>
+        private async Task LogOperationAsync(
+            SqlSugarClient sqlSugar,
+            HttpContext context,
+            ApiLogAttribute apiLogAttribute,
+            string requestBodyText,
+            string responseBodyText,
+            DateTime startTime,
+            int portType,
+            int userId,
+            int id,
+            int status,
+            string updatePreData,
+            string updateBefData)
+        {
+            try
+            {
+                // 设置较短的超时时间
+                sqlSugar.Ado.CommandTimeOut = 3;
 
-                    // 提取设备信息
-                    deviceType = client.Device.Family; // 设备类型,如 'mobile', 'tablet', 'desktop' 等
+                // 获取IP信息
+                string remoteIp = GetClientIp(context);
+                string location = await GetIpLocationSafe(remoteIp);
 
-                }
+                // 获取设备信息
+                var (deviceType, browser, os) = GetDeviceInfo(context);
 
                 // 记录请求结束时间
                 var endTime = DateTime.UtcNow;
                 // 计算耗时
                 var duration = (long)(endTime - startTime).TotalMilliseconds;
 
+                // 截断过长的文本
+                var truncatedRequestBody = TruncateString(requestBodyText, 4000);
+                var truncatedResponseBody = TruncateString(responseBodyText, 4000);
+                var truncatedPreData = TruncateString(updatePreData, 4000);
+                var truncatedBefData = TruncateString(updateBefData, 4000);
+
                 var logInfo = new Crm_TableOperationRecord()
                 {
                     TableName = apiLogAttribute.TableName,
                     PortType = portType,
                     OperationItem = apiLogAttribute.OperationEnum,
                     DataId = id,
-                    RequestUrl = context.Request.Path,
+                    RequestUrl = context.Request.Path + context.Request.QueryString,
                     RemoteIp = remoteIp,
                     Location = location,
-                    RequestParam = !string.IsNullOrEmpty(requestBodyText) ? JsonConvert.SerializeObject(requestBodyText) : null,
-                    ReturnResult = !string.IsNullOrEmpty(responseBodyText) ? JsonConvert.SerializeObject(responseBodyText) : null,
+                    RequestParam = !string.IsNullOrEmpty(truncatedRequestBody) ? JsonConvert.SerializeObject(truncatedRequestBody) : null,
+                    ReturnResult = !string.IsNullOrEmpty(truncatedResponseBody) ? JsonConvert.SerializeObject(truncatedResponseBody) : null,
                     Elapsed = duration,
                     Status = context.Response.StatusCode.ToString(),
                     CreateUserId = userId,
-                    UpdatePreData = updatePreData,
-                    UpdateBefData = updateBefData,
+                    UpdatePreData = truncatedPreData,
+                    UpdateBefData = truncatedBefData,
                     Browser = browser,
                     Os = os,
                     DeviceType = deviceType,
+                    CreateTime = DateTime.Now
                 };
 
                 // 存储到数据库
-                await _sqlSugar.Insertable(logInfo).ExecuteCommandAsync();
+                await sqlSugar.Insertable(logInfo).ExecuteCommandAsync();
             }
-            else
+            catch (Exception ex)
             {
-                await _next(context);
+                _logger.LogError(ex, "记录操作日志失败");
+
+                // 降级:写入文件日志
+                try
+                {
+                    await WriteLogToFile(apiLogAttribute, requestBodyText, responseBodyText, userId, ex.Message);
+                }
+                catch (Exception fileEx)
+                {
+                    _logger.LogError(fileEx, "写入文件日志失败");
+                }
             }
         }
 
-        private async Task<string> TableInfoToJson(string tableName, int id)
+        /// <summary>
+        /// 将日志写入文件
+        /// </summary>
+        private async Task WriteLogToFile(ApiLogAttribute apiLogAttribute, string requestBody, string responseBody, int userId, string error)
+        {
+            var logDir = Path.Combine(Directory.GetCurrentDirectory(), "Logs", "OperationLogs");
+            Directory.CreateDirectory(logDir);
+
+            var logFile = Path.Combine(logDir, $"operation_fallback_{DateTime.Now:yyyyMMdd}.log");
+
+            var logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | " +
+                          $"Table: {apiLogAttribute.TableName} | " +
+                          $"Operation: {apiLogAttribute.OperationEnum} | " +
+                          $"User: {userId} | " +
+                          $"Request: {TruncateString(requestBody, 500)} | " +
+                          $"Response: {TruncateString(responseBody, 500)} | " +
+                          $"Error: {error}" +
+                          Environment.NewLine;
+
+            await File.AppendAllTextAsync(logFile, logEntry);
+        }
+
+        /// <summary>
+        /// 获取表数据JSON
+        /// </summary>
+        private async Task<string> TableInfoToJson(SqlSugarClient sqlSugar, string tableName, int id)
         {
-            if (_sqlSugar.DbMaintenance.IsAnyTable(tableName))
+            if (sqlSugar.DbMaintenance.IsAnyTable(tableName))
             {
-                string jsonLabel = string.Empty;
-                if (tableName.Equals("Crm_NewClientData"))
+                try
                 {
-                    var info = await _sqlSugar.Queryable<Crm_NewClientData>().FirstAsync(x => x.Id == id);
-                    if (info != null)
+                    string jsonLabel = string.Empty;
+                    if (tableName.Equals("Crm_NewClientData"))
+                    {
+                        var info = await sqlSugar.Queryable<Crm_NewClientData>()
+                            .Where(x => x.Id == id)
+                            .FirstAsync();
+
+                        if (info != null)
+                        {
+                            EncryptionProcessor.DecryptProperties(info);
+                            if (info != null)
+                                jsonLabel = JsonConvert.SerializeObject(info);
+                        }
+                    }
+                    else
                     {
-                        EncryptionProcessor.DecryptProperties(info);
-                        if (info != null) jsonLabel = JsonConvert.SerializeObject(info);
+                        var sql = $"SELECT * FROM {tableName} WHERE Id = {id}";
+                        var info = await sqlSugar.SqlQueryable<dynamic>(sql).FirstAsync();
+                        if (info != null)
+                            jsonLabel = JsonConvert.SerializeObject(info);
                     }
+                    return jsonLabel;
                 }
-                else
+                catch (Exception ex)
                 {
-                    var sql = string.Format(" Select * From {0} Where Id={1}", tableName, id);
-
-                    var info = await _sqlSugar.SqlQueryable<dynamic>(sql).FirstAsync();
-                    if (info != null) jsonLabel = JsonConvert.SerializeObject(info);
+                    _logger.LogError(ex, $"获取表数据失败: {tableName}, Id: {id}");
+                    return string.Empty;
                 }
-                return jsonLabel;
             }
-
             return string.Empty;
         }
 
+        /// <summary>
+        /// 是否是静态文件请求
+        /// </summary>
+        private bool IsStaticFileRequest(PathString path)
+        {
+            var staticExtensions = new[] { ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".woff", ".woff2", ".ttf", ".eot" };
+            return staticExtensions.Any(ext => path.Value.EndsWith(ext, StringComparison.OrdinalIgnoreCase));
+        }
+
+        /// <summary>
+        /// 读取请求体
+        /// </summary>
         private async Task<string> ReadRequestBody(HttpRequest request)
         {
-            request.EnableBuffering();
-
-            using var reader = new StreamReader(
-                request.Body,
-                Encoding.UTF8,
-                detectEncodingFromByteOrderMarks: false,
-                bufferSize: 8192,
-                leaveOpen: true
-            );
-
-            var body = await reader.ReadToEndAsync();
-            request.Body.Position = 0;
-            return body;
+            try
+            {
+                request.Body.Position = 0;
+
+                using var reader = new StreamReader(
+                    request.Body,
+                    Encoding.UTF8,
+                    detectEncodingFromByteOrderMarks: false,
+                    bufferSize: 8192,
+                    leaveOpen: true
+                );
+
+                var body = await reader.ReadToEndAsync();
+                request.Body.Position = 0;
+                return body;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "读取请求体失败");
+                return string.Empty;
+            }
         }
 
+        /// <summary>
+        /// 读取响应体
+        /// </summary>
         private async Task<string> ReadResponseBody(Stream stream)
         {
-            using var reader = new StreamReader(
-                stream,
-                Encoding.UTF8,
-                detectEncodingFromByteOrderMarks: false,
-                bufferSize: 8192,
-                leaveOpen: true
-            );
-
-            var body = await reader.ReadToEndAsync();
-            stream.Position = 0;
-            return body;
+            try
+            {
+                using var reader = new StreamReader(
+                    stream,
+                    Encoding.UTF8,
+                    detectEncodingFromByteOrderMarks: false,
+                    bufferSize: 8192,
+                    leaveOpen: true
+                );
+
+                var body = await reader.ReadToEndAsync();
+                stream.Position = 0;
+                return body;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "读取响应体失败");
+                return string.Empty;
+            }
         }
 
+        /// <summary>
+        /// 读取Token
+        /// </summary>
         private async Task<int> ReadToken(HttpContext context)
         {
-            var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
+            try
+            {
+                var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
 
-            // 检查Authorization头是否存在且以"Bearer "开头
-            if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
+                if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
+                {
+                    var authInfo = JwtHelper.SerializeJwt(authHeader);
+                    if (authInfo != null)
+                        return authInfo.UserId;
+                }
+            }
+            catch (Exception ex)
             {
-                var authInfo = JwtHelper.SerializeJwt(authHeader);
-                if (authInfo != null) return authInfo.UserId;
+                _logger.LogError(ex, "读取Token失败");
             }
 
             return 0;
         }
 
         /// <summary>
-        /// 获取IP信息
+        /// 获取客户端IP
+        /// </summary>
+        private string GetClientIp(HttpContext context)
+        {
+            try
+            {
+                if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
+                {
+                    var xForwardedFor = context.Request.Headers["X-Forwarded-For"].ToString();
+                    if (!string.IsNullOrEmpty(xForwardedFor))
+                    {
+                        var ips = xForwardedFor.Split(',', StringSplitOptions.RemoveEmptyEntries);
+                        if (ips.Length > 0)
+                            return ips[0].Trim();
+                    }
+                }
+
+                return context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
+            }
+            catch
+            {
+                return "Unknown";
+            }
+        }
+
+        /// <summary>
+        /// 安全获取IP位置
         /// </summary>
-        /// <param name="ip">ipv4 or ipv6</param>
-        /// <returns></returns>
-        private async Task<(string ip, string local)> GetIpInfo(string ip)
+        private async Task<string> GetIpLocationSafe(string ip)
         {
-            string local = string.Empty;
-
-            //if (IPAddress.TryParse(ip,out _))
-            //{
-            //    var response = await _httpClient.GetAsync($"https://api.vore.top/api/IPdata?ip={ip}");
-
-            //    response.EnsureSuccessStatusCode();
-            //    var json = await response.Content.ReadAsStringAsync();
-            //    var ipInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
-            //    if (ipInfo.code == 200)
-            //    {
-            //        ip = ipInfo.ipinfo.text;
-            //        local = $"{ipInfo.adcode.o}";
-            //    }
-            //}
-
-            return (ip, local);
+            if (string.IsNullOrWhiteSpace(ip) || ip == "Unknown" || ip == "::1" || ip == "127.0.0.1")
+                return "本地";
+
+            try
+            {
+                // IPv6地址
+                if (ip.Contains(":"))
+                    return "IPv6";
+
+                // 内网地址
+                if (ip.StartsWith("192.168.") || ip.StartsWith("10.") || ip.StartsWith("172.16.") || ip.StartsWith("172.17.") || ip.StartsWith("172.18.") || ip.StartsWith("172.19.") || ip.StartsWith("172.20.") || ip.StartsWith("172.21.") || ip.StartsWith("172.22.") || ip.StartsWith("172.23.") || ip.StartsWith("172.24.") || ip.StartsWith("172.25.") || ip.StartsWith("172.26.") || ip.StartsWith("172.27.") || ip.StartsWith("172.28.") || ip.StartsWith("172.29.") || ip.StartsWith("172.30.") || ip.StartsWith("172.31."))
+                    return "内网";
+
+                // 使用简单的IP查询,避免频繁调用外部API
+                return "未知";
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, $"获取IP位置失败: {ip}");
+                return "未知";
+            }
         }
 
-        private async Task<string> GetExternalIp()
+        /// <summary>
+        /// 获取设备信息
+        /// </summary>
+        private (string deviceType, string browser, string os) GetDeviceInfo(HttpContext context)
         {
-            var response = await _httpClient.GetAsync("https://ifconfig.me");
-            response.EnsureSuccessStatusCode();
-            return await response.Content.ReadAsStringAsync();
+            try
+            {
+                var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault();
+                if (string.IsNullOrEmpty(userAgent))
+                    return ("Unknown", "Unknown", "Unknown");
+
+                var parser = Parser.GetDefault();
+                var client = parser.Parse(userAgent);
+
+                // 提取浏览器信息
+                var browser = client.UA.Family;
+                if (!string.IsNullOrEmpty(client.UA.Major))
+                {
+                    browser += $" {client.UA.Major}";
+                    if (!string.IsNullOrEmpty(client.UA.Minor))
+                        browser += $".{client.UA.Minor}";
+                }
+
+                // 提取操作系统信息
+                var os = client.OS.Family;
+                if (!string.IsNullOrEmpty(client.OS.Major))
+                {
+                    os += $" {client.OS.Major}";
+                    if (!string.IsNullOrEmpty(client.OS.Minor))
+                        os += $".{client.OS.Minor}";
+                }
+
+                // 提取设备信息
+                var deviceType = client.Device.Family;
+
+                return (deviceType, browser, os);
+            }
+            catch
+            {
+                return ("Unknown", "Unknown", "Unknown");
+            }
         }
 
-        private async Task<string> GetIpLocation(string ip)
+        /// <summary>
+        /// 截断字符串
+        /// </summary>
+        private string TruncateString(string input, int maxLength)
         {
-            var response = await _httpClient.GetAsync($"https://ipinfo.io/{ip}/json?&language=zh-CN");
-            response.EnsureSuccessStatusCode();
-            var json = await response.Content.ReadAsStringAsync();
-            var ipInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
-            return $"{ipInfo.country}, {ipInfo.city}";
+            if (string.IsNullOrEmpty(input) || input.Length <= maxLength)
+                return input;
+
+            return input.Substring(0, maxLength) + "...[已截断]";
         }
     }
-}
+}