|
|
@@ -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, "写入文件日志失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|