更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Net.Http;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace YY.Admin.Services.Service.Jeecg;
|
||||
|
||||
/// <summary>
|
||||
/// 登录日志上报:
|
||||
/// 1) WebSocket 优先;
|
||||
/// 2) 失败自动 HTTP 兜底;
|
||||
/// 3) 仍失败则写本地队列,后台自动补传。
|
||||
/// </summary>
|
||||
public class JeecgLoginLogReportService : IJeecgLoginLogReportService, IClientLogReportSink, ISingletonDependency
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IJeecgBackendGateway _jeecgBackendGateway;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly SemaphoreSlim _queueLock = new(1, 1);
|
||||
private readonly CancellationTokenSource _syncCts = new();
|
||||
private int _started;
|
||||
|
||||
private sealed class LoginLogQueueItem
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public string Category { get; set; } = "LOGIN";
|
||||
public string Account { get; set; } = string.Empty;
|
||||
public bool? Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string? Exception { get; set; }
|
||||
public int LogType { get; set; } = 1;
|
||||
public int OperateType { get; set; } = 5;
|
||||
public string Method { get; set; } = "LOGIN";
|
||||
public string RequestUrl { get; set; } = "/desktop/log";
|
||||
public long Timestamp { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
public string ClientType { get; set; } = "gkj";
|
||||
}
|
||||
|
||||
public JeecgLoginLogReportService(
|
||||
IConfiguration configuration,
|
||||
IJeecgBackendGateway jeecgBackendGateway,
|
||||
HttpClient httpClient)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_jeecgBackendGateway = jeecgBackendGateway;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public void StartBackgroundSync()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _started, 1) == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(() => BackgroundSyncLoopAsync(_syncCts.Token), _syncCts.Token);
|
||||
}
|
||||
|
||||
public async Task ReportLoginAsync(string account, bool success, string message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ReportLogAsync("LOGIN", message, account, success, null, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ReportLogAsync(
|
||||
string category,
|
||||
string message,
|
||||
string? account = null,
|
||||
bool? success = null,
|
||||
string? exception = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalizedCategory = string.IsNullOrWhiteSpace(category) ? "OPERATION" : category.Trim().ToUpperInvariant();
|
||||
var item = BuildQueueItem(normalizedCategory, account, success, message, exception);
|
||||
if (await TrySendToBackendAsync(item, cancellationToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await EnqueueAsync(item, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task BackgroundSyncLoopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await FlushQueueAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"日志离线补传失败: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> TrySendToBackendAsync(LoginLogQueueItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
cmd = "SCADA_LOG",
|
||||
category = item.Category,
|
||||
account = item.Account,
|
||||
success = item.Success,
|
||||
message = item.Message,
|
||||
exception = item.Exception,
|
||||
logType = item.LogType,
|
||||
operateType = item.OperateType,
|
||||
method = item.Method,
|
||||
requestUrl = item.RequestUrl,
|
||||
clientType = item.ClientType,
|
||||
timestamp = item.Timestamp
|
||||
};
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
|
||||
if (await _jeecgBackendGateway.SendWebSocketOneShotAsync(json, cancellationToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var baseUrl = _configuration.GetValue<string>("JeecgIntegration:BaseUrl")?.TrimEnd('/');
|
||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var url = $"{baseUrl}/sys/log/scada/addLoginLog";
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var resp = await _httpClient.SendAsync(req, cancellationToken);
|
||||
return resp.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnqueueAsync(LoginLogQueueItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
await _queueLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var path = GetQueuePath();
|
||||
if (File.Exists(path))
|
||||
{
|
||||
lines = File.ReadAllLines(path).Where(l => !string.IsNullOrWhiteSpace(l)).ToList();
|
||||
}
|
||||
lines.Add(JsonSerializer.Serialize(item));
|
||||
EnsureQueueDir(path);
|
||||
File.WriteAllLines(path, lines);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FlushQueueAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _queueLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
var path = GetQueuePath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = File.ReadAllLines(path).Where(l => !string.IsNullOrWhiteSpace(l)).ToList();
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var remain = new List<string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
LoginLogQueueItem? item = null;
|
||||
try
|
||||
{
|
||||
item = JsonSerializer.Deserialize<LoginLogQueueItem>(line);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败的数据直接丢弃,避免阻塞后续
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item == null || !await TrySendToBackendAsync(item, cancellationToken))
|
||||
{
|
||||
remain.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (remain.Count == 0)
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.WriteAllLines(path, remain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetQueuePath()
|
||||
{
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AppSettings", "offline-scada-log-queue.jsonl");
|
||||
}
|
||||
|
||||
private static void EnsureQueueDir(string path)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
private static LoginLogQueueItem BuildQueueItem(
|
||||
string category,
|
||||
string? account,
|
||||
bool? success,
|
||||
string? message,
|
||||
string? exception)
|
||||
{
|
||||
var item = new LoginLogQueueItem
|
||||
{
|
||||
Category = category,
|
||||
Account = account ?? string.Empty,
|
||||
Success = success,
|
||||
Message = message ?? string.Empty,
|
||||
Exception = exception
|
||||
};
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case "LOGIN":
|
||||
item.LogType = 1;
|
||||
item.OperateType = 1;
|
||||
item.Method = "LOGIN";
|
||||
item.RequestUrl = "/desktop/login";
|
||||
break;
|
||||
case "EXCEPTION":
|
||||
item.LogType = 2;
|
||||
item.OperateType = 5;
|
||||
item.Method = "ERROR";
|
||||
item.RequestUrl = "/desktop/exception";
|
||||
break;
|
||||
case "WARNING":
|
||||
item.LogType = 2;
|
||||
item.OperateType = 4;
|
||||
item.Method = "WARNING";
|
||||
item.RequestUrl = "/desktop/warning";
|
||||
break;
|
||||
default:
|
||||
item.LogType = 1;
|
||||
item.OperateType = 5;
|
||||
item.Method = "OPERATION";
|
||||
item.RequestUrl = "/desktop/operation";
|
||||
break;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user