Ver código fonte

新增字段新增默认值

Lyyyi 7 meses atrás
pai
commit
f04314f197

+ 268 - 207
OASystem/OASystem.Api/Middlewares/RecordAPIOperationMiddleware.cs

@@ -20,28 +20,27 @@ namespace OASystem.API.Middlewares
         private readonly HttpClient _httpClient;
         private readonly IConfiguration _config;
         private readonly ILogger<RecordAPIOperationMiddleware> _logger;
+        private readonly IServiceProvider _serviceProvider;
 
         /// <summary>
         /// 初始化
         /// </summary>
-        /// <param name="next"></param>
-        /// <param name="config"></param>
-        /// <param name="httpClientFactory"></param>
-        /// <param name="logger"></param>
         public RecordAPIOperationMiddleware(
             RequestDelegate next,
             IConfiguration config,
             IHttpClientFactory httpClientFactory,
-            ILogger<RecordAPIOperationMiddleware> logger)
+            ILogger<RecordAPIOperationMiddleware> logger,
+            IServiceProvider serviceProvider)
         {
             _next = next;
             _config = config;
             _logger = logger;
+            _serviceProvider = serviceProvider;
             _httpClient = httpClientFactory.CreateClient();
             _httpClient.Timeout = TimeSpan.FromSeconds(5);
         }
 
-        public async Task InvokeAsync(HttpContext context, IServiceProvider serviceProvider)
+        public async Task InvokeAsync(HttpContext context)
         {
             // 跳过 OPTIONS 请求(CORS 预检请求)
             if (context.Request.Method.Equals(HttpMethods.Options, StringComparison.OrdinalIgnoreCase))
@@ -72,97 +71,93 @@ namespace OASystem.API.Middlewares
                 int portType = 1, userId = 0, id = 0, status = 0;
                 string updatePreData = string.Empty, updateBefData = string.Empty;
 
-                try
+                // 获取用户ID和其他参数
+                using (var scope = _serviceProvider.CreateScope())
                 {
-                    userId = await ReadToken(context);
+                    var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
 
-                    if (!string.IsNullOrEmpty(requestBodyText))
+                    try
                     {
-                        var requestBodyJson = JsonConvert.DeserializeObject<Dictionary<string, object>>(requestBodyText);
+                        userId = await ReadToken(context);
 
-                        if (requestBodyJson != null)
+                        if (!string.IsNullOrEmpty(requestBodyText))
                         {
-                            // 提取参数
-                            if (requestBodyJson.TryGetValue("portType", out var param1Obj) && param1Obj != null)
-                            {
-                                int.TryParse(param1Obj.ToString(), out portType);
-                            }
+                            var requestBodyJson = JsonConvert.DeserializeObject<Dictionary<string, object>>(requestBodyText);
 
-                            if (requestBodyJson.TryGetValue("id", out var param5Obj) && param5Obj != null)
+                            if (requestBodyJson != null)
                             {
-                                int.TryParse(param5Obj.ToString(), out id);
-                            }
-
-                            if (requestBodyJson.TryGetValue("status", out var param6Obj) && param6Obj != null)
-                            {
-                                int.TryParse(param6Obj.ToString(), out status);
-                            }
-
-                            // 用户Id处理
-                            if (userId < 1)
-                            {
-                                if (apiLogAttribute.OperationEnum == OperationEnum.Login)
+                                // 提取参数
+                                if (requestBodyJson.TryGetValue("portType", out var param1Obj) && param1Obj != null)
                                 {
-                                    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>();
+                                    int.TryParse(param1Obj.ToString(), out portType);
+                                }
 
-                                        var info = await sqlSugar.Queryable<Sys_Users>()
-                                            .Where(x => x.IsDel == 0 && x.Number.Equals(number))
-                                            .FirstAsync();
+                                if (requestBodyJson.TryGetValue("id", out var param5Obj) && param5Obj != null)
+                                {
+                                    int.TryParse(param5Obj.ToString(), out id);
+                                }
 
-                                        userId = info?.Id ?? 0;
-                                    }
+                                if (requestBodyJson.TryGetValue("status", out var param6Obj) && param6Obj != null)
+                                {
+                                    int.TryParse(param6Obj.ToString(), out status);
                                 }
-                                else
+
+                                // 用户Id处理
+                                if (userId < 1)
                                 {
-                                    userId = ParseUserIdFromParams(requestBodyJson);
+                                    if (apiLogAttribute.OperationEnum == OperationEnum.Login)
+                                    {
+                                        var number = requestBodyJson.TryGetValue("number", out var numberObj) ? numberObj?.ToString() : null;
+                                        if (!string.IsNullOrEmpty(number))
+                                        {
+                                            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);
+                                    }
                                 }
-                            }
 
-                            // 根据status判断操作类型
-                            if (status > 0)
-                            {
-                                if (status == 1)
-                                    apiLogAttribute.OperationEnum = OperationEnum.Add;
-                                else if (status == 2)
+                                // 根据status判断操作类型
+                                if (status > 0)
                                 {
-                                    apiLogAttribute.OperationEnum = OperationEnum.Edit;
-                                    if (id > 0)
+                                    if (status == 1)
+                                        apiLogAttribute.OperationEnum = OperationEnum.Add;
+                                    else if (status == 2)
                                     {
-                                        // 使用独立的数据库连接获取修改前数据
-                                        using var scope = serviceProvider.CreateScope();
-                                        var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
-                                        updatePreData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
+                                        apiLogAttribute.OperationEnum = OperationEnum.Edit;
+                                        if (id > 0)
+                                        {
+                                            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;
+                                // 根据id判断操作类型
+                                else if (id > 0 && apiLogAttribute.OperationEnum != OperationEnum.Del)
+                                {
+                                    apiLogAttribute.OperationEnum = OperationEnum.Edit;
+                                    updatePreData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
+                                }
+                                else if (apiLogAttribute.OperationEnum != OperationEnum.Del && id < 1)
+                                {
+                                    apiLogAttribute.OperationEnum = OperationEnum.Add;
+                                }
                             }
                         }
                     }
-                }
-                catch (JsonException)
-                {
-                    _logger.LogDebug("JSON解析失败,可能请求体不是JSON格式");
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "解析请求参数时发生错误");
+                    catch (JsonException)
+                    {
+                        _logger.LogDebug("JSON解析失败,可能请求体不是JSON格式");
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "解析请求参数时发生错误");
+                    }
                 }
 
                 // 保存原始响应体流
@@ -187,100 +182,24 @@ namespace OASystem.API.Middlewares
                 // 修改后数据查询
                 if (status == 2 && id > 0)
                 {
-                    using var scope = serviceProvider.CreateScope();
-                    var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
-                    updateBefData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
-                }
-                else if (apiLogAttribute.OperationEnum == OperationEnum.Edit && id > 0)
-                {
-                    using var scope = serviceProvider.CreateScope();
-                    var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
-                    updateBefData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
-                }
-
-                // 异步记录日志,不阻塞响应
-                _ = Task.Run(async () =>
-                {
-                    try
+                    using (var scope = _serviceProvider.CreateScope())
                     {
-                        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, "异步记录操作日志失败");
+                        var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+                        updateBefData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
                     }
-                });
-            }
-            else
-            {
-                await _next(context);
-            }
-        }
-
-        /// <summary>
-        /// 从请求参数中解析用户ID
-        /// </summary>
-        private int ParseUserIdFromParams(Dictionary<string, object> requestBodyJson)
-        {
-            var userIdKeys = new[] { "userId", "currUserId", "createUserId", "operationUserId", "deleteUserId" };
-
-            foreach (var key in userIdKeys)
-            {
-                if (requestBodyJson.TryGetValue(key, out var value) && value != null)
+                }
+                else if (apiLogAttribute.OperationEnum == OperationEnum.Edit && id > 0)
                 {
-                    if (int.TryParse(value.ToString(), out var parsedUserId) && parsedUserId > 0)
+                    using (var scope = _serviceProvider.CreateScope())
                     {
-                        return parsedUserId;
+                        var sqlSugar = scope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+                        updateBefData = await TableInfoToJson(sqlSugar, apiLogAttribute.TableName, id);
                     }
                 }
-            }
-
-            return 0;
-        }
-
-        /// <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;
 
-                // 获取IP信息
+                // 获取IP信息和设备信息
                 string remoteIp = GetClientIp(context);
                 string location = await GetIpLocationSafe(remoteIp);
-
-                // 获取设备信息
                 var (deviceType, browser, os) = GetDeviceInfo(context);
 
                 // 记录请求结束时间
@@ -316,45 +235,62 @@ namespace OASystem.API.Middlewares
                     CreateTime = DateTime.Now
                 };
 
-                // 存储到数据库
-                await sqlSugar.Insertable(logInfo).ExecuteCommandAsync();
+                // 异步记录日志,不阻塞响应
+                _ = Task.Run(async () =>
+                {
+                    try
+                    {
+                        // 在异步任务中创建新的作用域
+                        using var logScope = _serviceProvider.CreateScope();
+                        var logSqlSugar = logScope.ServiceProvider.GetRequiredService<SqlSugarClient>();
+
+                        // 设置较短的超时时间
+                        logSqlSugar.Ado.CommandTimeOut = 3;
+
+                        // 存储到数据库
+                        await logSqlSugar.Insertable(logInfo).ExecuteCommandAsync();
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "异步记录操作日志失败");
+
+                        // 降级:写入文件日志
+                        try
+                        {
+                            await WriteLogToFile(apiLogAttribute, requestBodyText, responseBodyText, userId, ex.Message);
+                        }
+                        catch (Exception fileEx)
+                        {
+                            _logger.LogError(fileEx, "写入文件日志失败");
+                        }
+                    }
+                });
             }
-            catch (Exception ex)
+            else
             {
-                _logger.LogError(ex, "记录操作日志失败");
-
-                // 降级:写入文件日志
-                try
-                {
-                    await WriteLogToFile(apiLogAttribute, requestBodyText, responseBodyText, userId, ex.Message);
-                }
-                catch (Exception fileEx)
-                {
-                    _logger.LogError(fileEx, "写入文件日志失败");
-                }
+                await _next(context);
             }
         }
 
         /// <summary>
-        /// 将日志写入文件
+        /// 从请求参数中解析用户ID
         /// </summary>
-        private async Task WriteLogToFile(ApiLogAttribute apiLogAttribute, string requestBody, string responseBody, int userId, string error)
+        private int ParseUserIdFromParams(Dictionary<string, object> requestBodyJson)
         {
-            var logDir = Path.Combine(Directory.GetCurrentDirectory(), "Logs", "OperationLogs");
-            Directory.CreateDirectory(logDir);
-
-            var logFile = Path.Combine(logDir, $"operation_fallback_{DateTime.Now:yyyyMMdd}.log");
+            var userIdKeys = new[] { "userId", "currUserId", "createUserId", "operationUserId", "deleteUserId" };
 
-            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;
+            foreach (var key in userIdKeys)
+            {
+                if (requestBodyJson.TryGetValue(key, out var value) && value != null)
+                {
+                    if (int.TryParse(value.ToString(), out var parsedUserId) && parsedUserId > 0)
+                    {
+                        return parsedUserId;
+                    }
+                }
+            }
 
-            await File.AppendAllTextAsync(logFile, logEntry);
+            return 0;
         }
 
         /// <summary>
@@ -403,8 +339,23 @@ namespace OASystem.API.Middlewares
         /// </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));
+            try
+            {
+                var staticExtensions = new[] {
+                    ".css", ".js", ".png", ".jpg", ".jpeg", ".gif",
+                    ".ico", ".svg", ".woff", ".woff2", ".ttf", ".eot",
+                    ".mp4", ".mp3", ".avi", ".mov", ".wmv", ".flv",
+                    ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
+                    ".zip", ".rar", ".7z", ".tar", ".gz"
+                };
+
+                var pathString = path.Value?.ToLower() ?? string.Empty;
+                return staticExtensions.Any(ext => pathString.EndsWith(ext.ToLower(), StringComparison.OrdinalIgnoreCase));
+            }
+            catch
+            {
+                return false;
+            }
         }
 
         /// <summary>
@@ -492,6 +443,7 @@ namespace OASystem.API.Middlewares
         {
             try
             {
+                // 优先从X-Forwarded-For获取
                 if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
                 {
                     var xForwardedFor = context.Request.Headers["X-Forwarded-For"].ToString();
@@ -503,7 +455,25 @@ namespace OASystem.API.Middlewares
                     }
                 }
 
-                return context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
+                // 其次从X-Real-IP获取
+                if (context.Request.Headers.ContainsKey("X-Real-IP"))
+                {
+                    var xRealIp = context.Request.Headers["X-Real-IP"].ToString();
+                    if (!string.IsNullOrEmpty(xRealIp))
+                        return xRealIp.Trim();
+                }
+
+                // 最后从RemoteIpAddress获取
+                var remoteIp = context.Connection.RemoteIpAddress?.ToString();
+                if (!string.IsNullOrEmpty(remoteIp))
+                {
+                    // 处理IPv6映射的IPv4地址
+                    if (remoteIp.StartsWith("::ffff:"))
+                        return remoteIp.Substring(7);
+                    return remoteIp;
+                }
+
+                return "Unknown";
             }
             catch
             {
@@ -525,11 +495,12 @@ namespace OASystem.API.Middlewares
                 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."))
+                // 检查是否是内网地址
+                if (IsPrivateIp(ip))
                     return "内网";
 
-                // 使用简单的IP查询,避免频繁调用外部API
+                // 可以在这里调用IP查询API
+                // 但为了性能,暂时返回未知
                 return "未知";
             }
             catch (Exception ex)
@@ -539,6 +510,47 @@ namespace OASystem.API.Middlewares
             }
         }
 
+        /// <summary>
+        /// 检查是否是内网IP
+        /// </summary>
+        private bool IsPrivateIp(string ip)
+        {
+            try
+            {
+                if (string.IsNullOrWhiteSpace(ip))
+                    return false;
+
+                // 常见的私有IP地址段
+                if (ip.StartsWith("10.") ||
+                    ip.StartsWith("192.168.") ||
+                    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 true;
+                }
+
+                return false;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
         /// <summary>
         /// 获取设备信息
         /// </summary>
@@ -555,29 +567,49 @@ namespace OASystem.API.Middlewares
 
                 // 提取浏览器信息
                 var browser = client.UA.Family;
+                var browserVersion = new List<string>();
                 if (!string.IsNullOrEmpty(client.UA.Major))
-                {
-                    browser += $" {client.UA.Major}";
-                    if (!string.IsNullOrEmpty(client.UA.Minor))
-                        browser += $".{client.UA.Minor}";
-                }
+                    browserVersion.Add(client.UA.Major);
+                if (!string.IsNullOrEmpty(client.UA.Minor))
+                    browserVersion.Add(client.UA.Minor);
+                if (!string.IsNullOrEmpty(client.UA.Patch))
+                    browserVersion.Add(client.UA.Patch);
+
+                if (browserVersion.Any())
+                    browser += " " + string.Join(".", browserVersion);
 
                 // 提取操作系统信息
                 var os = client.OS.Family;
+                var osVersion = new List<string>();
                 if (!string.IsNullOrEmpty(client.OS.Major))
-                {
-                    os += $" {client.OS.Major}";
-                    if (!string.IsNullOrEmpty(client.OS.Minor))
-                        os += $".{client.OS.Minor}";
-                }
+                    osVersion.Add(client.OS.Major);
+                if (!string.IsNullOrEmpty(client.OS.Minor))
+                    osVersion.Add(client.OS.Minor);
+                if (!string.IsNullOrEmpty(client.OS.Patch))
+                    osVersion.Add(client.OS.Patch);
+                if (!string.IsNullOrEmpty(client.OS.PatchMinor))
+                    osVersion.Add(client.OS.PatchMinor);
+
+                if (osVersion.Any())
+                    os += " " + string.Join(".", osVersion);
 
                 // 提取设备信息
                 var deviceType = client.Device.Family;
+                if (string.Equals(deviceType, "Other", StringComparison.OrdinalIgnoreCase))
+                {
+                    // 根据userAgent判断设备类型
+                    var ua = userAgent.ToLower();
+                    if (ua.Contains("mobile") || ua.Contains("android") || ua.Contains("iphone") || ua.Contains("ipad"))
+                        deviceType = "Mobile";
+                    else
+                        deviceType = "Desktop";
+                }
 
                 return (deviceType, browser, os);
             }
-            catch
+            catch (Exception ex)
             {
+                _logger.LogDebug(ex, "解析设备信息失败");
                 return ("Unknown", "Unknown", "Unknown");
             }
         }
@@ -592,5 +624,34 @@ namespace OASystem.API.Middlewares
 
             return input.Substring(0, maxLength) + "...[已截断]";
         }
+
+        /// <summary>
+        /// 将日志写入文件
+        /// </summary>
+        private async Task WriteLogToFile(ApiLogAttribute apiLogAttribute, string requestBody, string responseBody, int userId, string error)
+        {
+            try
+            {
+                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);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "写入文件日志失败");
+            }
+        }
     }
 }

+ 2 - 2
OASystem/OASystem.Domain/Dtos/Groups/GroupListDto.cs

@@ -681,12 +681,12 @@ namespace OASystem.Domain.Dtos.Groups
         /// <summary>
         /// 额外超支额度
         /// </summary>
-        public decimal ExtOverLimit { get; set; }
+        public decimal ExtOverLimit { get; set; } = 0.00M;
 
         /// <summary>
         /// 额外超支额度币种
         /// </summary>
-        public int ExtOverCurrency { get; set; }
+        public int ExtOverCurrency { get; set; } = 836;
     }
 
 }