新增车辆表及OTA权限与逻辑优化

新增车辆任务与报警表,完善实体与DbContext配置。细化OTA权限校验,增强回传会话IP安全。优化OTA上传与设置面板,调度器支持重启恢复。报警采集逻辑支持历史分段。
This commit is contained in:
2026-07-27 15:12:14 +08:00
parent bc3e9c5172
commit 1f72488a8d
20 changed files with 2392 additions and 53 deletions
+29
View File
@@ -16,6 +16,35 @@ public sealed class OtaJobRunner
_wd = wd;
_vehicles = vehicles;
_log = log;
RecoverInterruptedJobs();
}
/// <summary>进程重启后把落盘中非终态 job 标为 failed,避免 UI 永久显示 running。</summary>
private void RecoverInterruptedJobs()
{
try
{
foreach (var job in _store.ListJobs(500))
{
if (job.Status is not ("running" or "pending")) continue;
job.Status = "failed";
job.Message = string.IsNullOrWhiteSpace(job.Message)
? "进程重启,任务中断"
: job.Message;
job.FinishedAt = DateTimeOffset.UtcNow;
foreach (var step in job.Steps.Where(s => s.Status is "pending" or "running"))
{
step.Status = "failed";
step.Error ??= "进程重启,任务中断";
}
_store.SaveJob(job);
_log.LogWarning("OTA job {Id} marked failed after process restart", job.Id);
}
}
catch (Exception ex)
{
_log.LogWarning(ex, "OTA interrupted job recovery failed");
}
}
public OtaJob EnqueueSync(CreateSyncJobRequest req, string? user)
+3 -1
View File
@@ -6,9 +6,11 @@ public sealed class OtaSettings
public int MaxCar { get; set; } = 2;
public bool LatencyEnabled { get; set; }
public int RttThresholdMs { get; set; } = 200;
/// <summary>skip | confirm</summary>
/// <summary>skip | allow(历史值 confirm 视为 allow</summary>
public string OverThreshold { get; set; } = "skip";
/// <summary>保留字段:备份尚未实现,仅反序列化兼容。</summary>
public int BackupPeriodMinutes { get; set; } = 60;
/// <summary>保留字段:备份尚未实现,仅反序列化兼容。</summary>
public bool BackupExe { get; set; }
public string? NewVersionName { get; set; }
}
+24 -7
View File
@@ -24,6 +24,8 @@ public sealed class OtaStore
private string? _lastPullId;
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, string> _pullByIp =
new(StringComparer.OrdinalIgnoreCase);
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, DateTime> _pullLastReceiveUtc =
new(StringComparer.OrdinalIgnoreCase);
private long _jobSeq;
public OtaStore(IWebHostEnvironment env, IOptions<OtaOptions> options, ILogger<OtaStore> log)
@@ -144,29 +146,44 @@ public sealed class OtaStore
lock (_gate)
{
var ip = NormalizeIp(sourceIp);
// 必须匹配会话 IP;禁止无 IP 时回退到最近一次拉包(可被伪造/误写)。
if (ip != null && _pullByIp.TryGetValue(ip, out var byIp))
{
id = byIp;
return true;
}
if (ip == null && _lastPullId != null)
{
id = _lastPullId;
return true;
}
id = null;
return false;
}
}
public void NotePullReceive(string? sourceIp)
{
var ip = NormalizeIp(sourceIp);
if (ip == null) return;
_pullLastReceiveUtc[ip] = DateTime.UtcNow;
}
public bool TryGetLastReceiveAt(string? sourceIp, out DateTime utc)
{
var ip = NormalizeIp(sourceIp);
if (ip != null && _pullLastReceiveUtc.TryGetValue(ip, out utc))
return true;
utc = default;
return false;
}
public void ClearActivePull(string? sourceIp = null)
{
lock (_gate)
{
var ip = NormalizeIp(sourceIp);
if (ip != null) _pullByIp.TryRemove(ip, out _);
if (ip != null)
{
_pullByIp.TryRemove(ip, out _);
_pullLastReceiveUtc.TryRemove(ip, out _);
}
if (_pullByIp.IsEmpty) _lastPullId = null;
}
}