update V 2.4.3.0

This commit is contained in:
rnchg
2026-07-21 19:00:11 +08:00
parent 4b28b75e57
commit 86bb4d07e4
1571 changed files with 511366 additions and 244 deletions
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
View File
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.2" />
<PackageReference Include="Common.Net" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Device" Version="2.4.2" />
<PackageReference Include="Common.Device" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Device" Version="2.4.2" />
<PackageReference Include="Common.Device" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
</Project>
@@ -5,19 +5,19 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CETCompat>false</CETCompat>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<AssemblyName>FASS.Scheduler</AssemblyName>
<ApplicationIcon>Resources\App.ico</ApplicationIcon>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FASS.Boot" Version="2.4.2" />
<PackageReference Include="FASS.Boot" Version="2.4.3" />
<PackageReference Include="Grpc.AspNetCore" Version="2.80.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>
<ItemGroup>
@@ -2,6 +2,7 @@ using Common.NETCore;
using Common.NETCore.Extensions;
using Common.NETCore.Helpers;
using Common.NETCore.Timing;
using Common.Service.Helpers;
using FASS.Boot.Services;
using FASS.Core.Services.Data;
using FASS.Data.Consts.Data;
@@ -26,19 +27,9 @@ public sealed class RemoteService : Remote.RemoteBase
private readonly Func<string?, string> _carStateName;
private readonly Func<string?, string> _alarmLevelName;
private readonly object _snapshotCacheLock = new();
private SnapshotCacheEntry? _snapshotCache;
private readonly record struct SnapshotCacheKey(long DataVersion, int AlarmLimit);
private sealed class SnapshotCacheEntry
{
internal SnapshotCacheEntry(SnapshotCacheKey key, RuntimeSnapshotReply reply)
{
Key = key;
Reply = reply;
}
internal SnapshotCacheKey Key { get; }
internal RuntimeSnapshotReply Reply { get; }
}
private long _snapshotCacheVersion;
private int _snapshotCacheAlarmLimit;
private RuntimeSnapshotReply? _snapshotCacheReply;
public RemoteService(
ILogger<RemoteService> logger,
@@ -149,17 +140,8 @@ public sealed class RemoteService : Remote.RemoteBase
private static List<T> ReadList<T>(string json) => Guard.NotNull(json.JsonTo<List<T>>());
private static string[] ToArrayOrEmpty(List<string> items) => items.Count == 0 ? Array.Empty<string>() : items.ToArray();
private static CancellationToken GetToken(ServerCallContext? context) => context?.CancellationToken ?? CancellationToken.None;
private static RuntimeSnapshotReply CreateFailedSnapshotReply(string message) => new()
{
Success = false,
Message = message,
Time = Clock.Now.ToMillisecondString()
};
public override Task<ResponseReply> CarAdd(CarAddRequest request, ServerCallContext context) =>
ReplyDtoAsync<CarDto>(nameof(CarAdd), request.Dto, _bootService.CarAddAsync, context);
@@ -265,8 +247,8 @@ public sealed class RemoteService : Remote.RemoteBase
priority,
isBreak,
isPreempt,
ToArrayOrEmpty(nodes),
ToArrayOrEmpty(edges),
nodes.Count == 0 ? Array.Empty<string>() : nodes.ToArray(),
edges.Count == 0 ? Array.Empty<string>() : edges.ToArray(),
token);
},
context);
@@ -323,7 +305,7 @@ public sealed class RemoteService : Remote.RemoteBase
watch.ElapsedMilliseconds,
reply.Cars.Count,
reply.Maps.Count,
reply.Alarms.Count);
reply.Alarms.Count);
}
DebugHelper.ThrowIfCancellationRequested(cancellationToken);
@@ -336,7 +318,12 @@ public sealed class RemoteService : Remote.RemoteBase
catch (Exception ex)
{
_logger.LogError(ex, "获取运行时快照失败。");
return Task.FromResult(CreateFailedSnapshotReply(ex.Message));
return Task.FromResult(new RuntimeSnapshotReply
{
Success = false,
Message = ex.Message,
Time = Clock.Now.ToMillisecondString()
});
}
}
@@ -344,19 +331,22 @@ public sealed class RemoteService : Remote.RemoteBase
{
DebugHelper.ThrowIfCancellationRequested(cancellationToken);
var cacheKey = new SnapshotCacheKey(snapshot.Version, alarmLimit);
lock (_snapshotCacheLock)
{
if (_snapshotCache is { } cacheEntry && cacheEntry.Key == cacheKey)
if (_snapshotCacheReply is not null
&& _snapshotCacheVersion == snapshot.Version
&& _snapshotCacheAlarmLimit == alarmLimit)
{
return cacheEntry.Reply;
return _snapshotCacheReply;
}
}
var reply = BuildSnapshotReplyCore(snapshot.Version, snapshot.EnabledCars, alarmLimit, cancellationToken);
lock (_snapshotCacheLock)
{
_snapshotCache = new SnapshotCacheEntry(cacheKey, reply);
_snapshotCacheVersion = snapshot.Version;
_snapshotCacheAlarmLimit = alarmLimit;
_snapshotCacheReply = reply;
}
return reply;
@@ -365,15 +355,13 @@ public sealed class RemoteService : Remote.RemoteBase
private RuntimeSnapshotReply BuildSnapshotReplyCore(long version, IReadOnlyList<Car> cars, int alarmLimit, CancellationToken cancellationToken)
{
var reply = CreateRuntimeSnapshotReply(version);
var orderedCars = RuntimeSnapshotProjection.OrderCars(cars);
var runtimeCars = RuntimeSnapshotProjection.ToRuntimeCars(orderedCars, ResolveCarState);
var runtimeMaps = RuntimeSnapshotProjection.ToRuntimeMaps(orderedCars);
var runtimeAlarms = RuntimeSnapshotProjection.SelectLatestCarAlarms(orderedCars, alarmLimit, _alarmLevelName);
for (var i = 0; i < runtimeCars.Count; i++)
var projectedCars = RuntimeSnapshotProjection.ProjectCars(cars, ResolveCarState);
var runtimeAlarms = RuntimeSnapshotProjection.SelectLatestCarAlarms(projectedCars.OrderedCars, alarmLimit, _alarmLevelName);
for (var i = 0; i < projectedCars.Cars.Count; i++)
{
DebugHelper.ThrowIfCancellationRequested(cancellationToken);
reply.Cars.Add(CreateRuntimeSnapshotCar(runtimeCars[i]));
reply.Maps.Add(CreateRuntimeSnapshotMap(runtimeMaps[i]));
reply.Cars.Add(CreateRuntimeSnapshotCar(projectedCars.Cars[i]));
reply.Maps.Add(CreateRuntimeSnapshotMap(projectedCars.Maps[i]));
}
for (var i = 0; i < runtimeAlarms.Count; i++)
@@ -483,7 +471,9 @@ public sealed class RemoteService : Remote.RemoteBase
{
return new RuntimeSnapshotAlarm
{
Key = alarm.Key ?? RuntimeSnapshotProjection.BuildRuntimeAlarmStateKey(alarm),
Key = string.IsNullOrWhiteSpace(alarm.Key)
? RecordStateKeyHelper.Build(alarm.Level, alarm.Type, alarm.Code, alarm.State)
: alarm.Key,
Id = alarm.Id ?? string.Empty,
Level = alarm.Level ?? string.Empty,
Type = alarm.Type,
@@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FASS.Data" Version="2.4.2" />
<PackageReference Include="FASS.Data" Version="2.4.3" />
</ItemGroup>
</Project>
@@ -8,6 +8,11 @@ namespace FASS.Service.Runtime.Helpers;
public static class RuntimeSnapshotProjection
{
public readonly record struct ProjectedCars(
IReadOnlyList<DataCar> OrderedCars,
IReadOnlyList<RuntimeModel.Car> Cars,
IReadOnlyList<RuntimeModel.Map> Maps);
public static string? NormalizeAlarmLevelCode(string? value)
{
if (string.IsNullOrWhiteSpace(value))
@@ -42,7 +47,7 @@ public static class RuntimeSnapshotProjection
return stateNameResolver(currState);
}
public static int CompareCarCode(string? left, string? right)
private static int CompareCarCode(string? left, string? right)
{
var normalizedLeft = left ?? string.Empty;
var normalizedRight = right ?? string.Empty;
@@ -50,11 +55,16 @@ public static class RuntimeSnapshotProjection
return byLength != 0 ? byLength : string.CompareOrdinal(normalizedLeft, normalizedRight);
}
public static List<DataCar> OrderCars(IEnumerable<DataCar> cars)
public static ProjectedCars ProjectCars(
IEnumerable<DataCar> cars,
Func<bool, bool, string?, string> carStateResolver)
{
var ordered = cars.ToList();
ordered.Sort(static (left, right) => CompareCarCode(left.Code, right.Code));
return ordered;
var orderedCars = cars.ToList();
orderedCars.Sort(static (left, right) => CompareCarCode(left.Code, right.Code));
return new ProjectedCars(
orderedCars,
ToRuntimeCars(orderedCars, carStateResolver),
ToRuntimeMaps(orderedCars));
}
public static IReadOnlyList<RuntimeModel.Car> ToRuntimeCars(
@@ -99,16 +109,18 @@ public static class RuntimeSnapshotProjection
for (var i = 0; i < cars.Count; i++)
{
var car = cars[i];
var edgeCount = car.CarEdges.Count;
var nodeCount = car.CarNodes.Count;
var routeSignature = car.RouteSignature != 0 || (car.CarEdges.Count == 0 && car.CarNodes.Count == 0)
var carEdges = car.CarEdges;
var carNodes = car.CarNodes;
var edgeCount = carEdges.Count;
var nodeCount = carNodes.Count;
var routeSignature = car.RouteSignature != 0 || (edgeCount == 0 && nodeCount == 0)
? car.RouteSignature
: DataCar.BuildRouteSignature(car.CarNodes, car.CarEdges);
: DataCar.BuildRouteSignature(carNodes, carEdges);
result[i] = new RuntimeModel.Map
{
Type = Normalize(car.CarType?.Code),
RouteSignature = routeSignature,
HasRouteTopology = car.CarEdges.Count > 0 || car.CarNodes.Count > 0,
HasRouteTopology = edgeCount > 0 || nodeCount > 0,
Code = new RuntimeModel.Code
{
Text = car.Code
@@ -145,7 +157,7 @@ public static class RuntimeSnapshotProjection
var runtimeEdges = runtimeBase.CarEdges;
for (var j = 0; j < edgeCount; j++)
{
var edge = car.CarEdges[j];
var edge = carEdges[j];
runtimeEdges.Add(new RuntimeModel.CarEdge
{
Id = NormalizeRuntimeId(edge.EdgeId),
@@ -163,7 +175,7 @@ public static class RuntimeSnapshotProjection
var runtimeNodes = runtimeBase.CarNodes;
for (var j = 0; j < nodeCount; j++)
{
var node = car.CarNodes[j];
var node = carNodes[j];
runtimeNodes.Add(new RuntimeModel.CarNode
{
Id = NormalizeRuntimeId(node.NodeId),
@@ -190,21 +202,23 @@ public static class RuntimeSnapshotProjection
for (var i = 0; i < alarms.Count; i++)
{
var alarm = alarms[i];
var level = NormalizeAlarmLevelCode(alarm.Level);
level = levelResolver(level);
var key = RecordStateKeyHelper.Build(level, alarm.Type, alarm.Code, alarm.State);
var runtimeAlarm = CreateRuntimeAlarm(
key,
alarm.Id,
levelResolver(NormalizeAlarmLevelCode(alarm.Level)),
level,
alarm.Type,
alarm.Code,
alarm.State,
alarm.Message,
alarm.Data);
var key = BuildRuntimeAlarmStateKey(runtimeAlarm);
if (deduplicated.ContainsKey(key))
if (!deduplicated.TryAdd(key, runtimeAlarm))
{
continue;
}
deduplicated[key] = runtimeAlarm;
if (deduplicated.Count >= alarmLimit)
{
break;
@@ -216,17 +230,6 @@ public static class RuntimeSnapshotProjection
return result;
}
public static string BuildRuntimeAlarmStateKey(
AlarmDto alarm,
Func<string?, string> levelResolver)
{
return RecordStateKeyHelper.Build(
levelResolver(NormalizeAlarmLevelCode(alarm.Level)),
alarm.Type,
alarm.Code,
alarm.State);
}
public static int CompareRuntimeAlarms(RuntimeModel.Alarm? left, RuntimeModel.Alarm? right)
{
return RuntimeAlarmOrdering.Compare(
@@ -240,16 +243,6 @@ public static class RuntimeSnapshotProjection
right?.State);
}
public static string BuildRuntimeAlarmStateKey(RuntimeModel.Alarm? alarm)
{
if (!string.IsNullOrWhiteSpace(alarm?.Key))
{
return alarm.Key;
}
return RecordStateKeyHelper.Build(alarm?.Level, alarm?.Type, alarm?.Code, alarm?.State);
}
public static void SortRuntimeAlarms(RuntimeModel.Alarm[] alarms)
{
if (alarms.Length <= 1)
@@ -282,11 +275,17 @@ public static class RuntimeSnapshotProjection
continue;
}
var level = NormalizeAlarmLevelCode(alarm.Level);
if (levelResolver is not null)
{
level = levelResolver(level);
}
var key = RecordStateKeyHelper.Build(level, alarm.Type, alarm.Code, alarm.State);
var runtimeAlarm = CreateRuntimeAlarm(
key,
alarm.Id,
levelResolver is null
? NormalizeAlarmLevelCode(alarm.Level)
: levelResolver(NormalizeAlarmLevelCode(alarm.Level)),
level,
alarm.Type,
alarm.Code,
alarm.State,
@@ -315,6 +314,7 @@ public static class RuntimeSnapshotProjection
public static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value;
private static RuntimeModel.Alarm CreateRuntimeAlarm(
string key,
string? id,
string? level,
string type,
@@ -325,7 +325,7 @@ public static class RuntimeSnapshotProjection
{
return new RuntimeModel.Alarm
{
Key = RecordStateKeyHelper.Build(level, type, code, state),
Key = key,
Id = id ?? string.Empty,
Level = level ?? string.Empty,
Type = type,
@@ -341,7 +341,9 @@ public static class RuntimeSnapshotProjection
RuntimeModel.Alarm alarm,
long createdAtTicks)
{
var stateKey = BuildRuntimeAlarmStateKey(alarm);
var stateKey = string.IsNullOrWhiteSpace(alarm.Key)
? RecordStateKeyHelper.Build(alarm.Level, alarm.Type, alarm.Code, alarm.State)
: alarm.Key;
var priority = new AlarmPriority(
createdAtTicks,
alarm.Level ?? string.Empty,
@@ -35,13 +35,6 @@ internal sealed class MonitorService : IAsyncDisposable
IReadOnlyList<RuntimeModel.Alarm> Alarms,
DateTime SnapshotAt);
private readonly record struct SnapshotPayload(
long Version,
string Time,
IReadOnlyList<RuntimeModel.Car> Cars,
IReadOnlyList<RuntimeModel.Map> Maps,
IReadOnlyList<RuntimeModel.Alarm> Alarms);
private readonly record struct SnapshotBuildResult(
RuntimeModel.Snapshot Snapshot,
long AlarmRevision);
@@ -113,6 +106,7 @@ internal sealed class MonitorService : IAsyncDisposable
private long _sendCount;
private long _dropped;
private long _sent;
private long _sendFailed;
private long _version;
private long _lastVersion;
private DbSnapshot _db = new(0, [], [], [], DateTime.MinValue);
@@ -170,9 +164,10 @@ internal sealed class MonitorService : IAsyncDisposable
Task<RuntimeModel.Snapshot> snapshotTask;
lock (_snapshotLock)
{
if (TryGetCachedNoLock(out var cached))
var cache = _snapshotCache;
if (cache.Snapshot is not null && Clock.UtcNow - cache.CachedAt <= _cacheInterval)
{
return Task.FromResult(cached);
return Task.FromResult(cache.Snapshot);
}
snapshotTask = _inFlight ??= LoadSnapshotAsync();
@@ -272,7 +267,18 @@ internal sealed class MonitorService : IAsyncDisposable
if (!IsStopped)
{
SetStopped(true);
await EnqueueUpdateAsync(CreateIdleSnapshot(), cancellationToken);
await EnqueueUpdateAsync(new RuntimeModel.Snapshot
{
IsRunning = IsRunning,
Version = NextUpdateVersion(),
Time = Clock.Now.ToMillisecondString(),
Cars = [],
Maps = [],
Alarms = [],
DataSource = IdleSource,
IsDegraded = false,
DataAgeMs = 0
}, cancellationToken);
}
return;
@@ -310,9 +316,11 @@ internal sealed class MonitorService : IAsyncDisposable
{
Interlocked.Exchange(ref _queued, 0);
var watch = Stopwatch.StartNew();
var sent = false;
try
{
await _publish(snapshot, cancellationToken);
sent = true;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
@@ -320,8 +328,8 @@ internal sealed class MonitorService : IAsyncDisposable
}
catch (Exception ex)
{
Interlocked.Increment(ref _sendFailed);
_log.LogWarning(ex, "运行监控推送失败。");
continue;
}
finally
{
@@ -329,7 +337,6 @@ internal sealed class MonitorService : IAsyncDisposable
}
RecordSendCost(watch.ElapsedMilliseconds);
Interlocked.Increment(ref _sent);
if (watch.ElapsedMilliseconds > _sendThresholdMs)
{
_log.LogWarning(
@@ -337,6 +344,11 @@ internal sealed class MonitorService : IAsyncDisposable
watch.ElapsedMilliseconds,
_sendThresholdMs);
}
if (sent)
{
Interlocked.Increment(ref _sent);
}
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -390,7 +402,17 @@ internal sealed class MonitorService : IAsyncDisposable
return ApplyAlarmPresentationPolicy(BuildDbModeSnapshot(cancellationToken));
}
if (TryTakePendingMemory(out var pending))
RuntimeModel.Snapshot? pending;
lock (_snapshotLock)
{
pending = _mode.PendingMemorySnapshot;
if (pending is not null)
{
_mode = _mode with { PendingMemorySnapshot = null };
}
}
if (pending is not null)
{
return ApplyAlarmPresentationPolicy(CompleteMemorySnapshot(pending, 0));
}
@@ -427,7 +449,11 @@ internal sealed class MonitorService : IAsyncDisposable
}
return CreateRuntimeSnapshot(
CreateSnapshotPayload(snapshot),
snapshot.Version,
snapshot.SnapshotAt == DateTime.MinValue ? Clock.Now.ToMillisecondString() : snapshot.SnapshotAt.ToMillisecondString(),
snapshot.Cars,
snapshot.Maps,
snapshot.Alarms,
DatabaseSource,
isDegraded: true,
dataAgeMs: GetDataAgeMs(snapshot.SnapshotAt));
@@ -461,7 +487,15 @@ internal sealed class MonitorService : IAsyncDisposable
}
private RuntimeModel.Snapshot CompleteMemorySnapshot(RuntimeModel.Snapshot snapshot, long dataAgeMs) =>
CreateRuntimeSnapshot(CreateSnapshotPayload(snapshot), MemorySource, isDegraded: false, dataAgeMs);
CreateRuntimeSnapshot(
snapshot.Version,
string.IsNullOrWhiteSpace(snapshot.Time) ? Clock.Now.ToMillisecondString() : snapshot.Time,
snapshot.Cars,
snapshot.Maps,
snapshot.Alarms,
MemorySource,
isDegraded: false,
dataAgeMs: dataAgeMs);
private RuntimeModel.Snapshot BuildMemoryFailureSnapshot(string reason, CancellationToken cancellationToken)
{
@@ -494,19 +528,17 @@ internal sealed class MonitorService : IAsyncDisposable
var carService = scope.ServiceProvider.GetRequiredService<ICarService>();
var alarmService = scope.ServiceProvider.GetRequiredService<IAlarmService>();
var cars = RuntimeSnapshotProjection.OrderCars(carService.GetRuntime(car => car.IsEnable));
var projectedCars = RuntimeSnapshotProjection.ProjectCars(carService.LoadRuntime(car => car.IsEnable), ResolveCarState);
var enabledCarCodes = new HashSet<string>(StringComparer.Ordinal);
for (var i = 0; i < cars.Count; i++)
for (var i = 0; i < projectedCars.OrderedCars.Count; i++)
{
var code = cars[i].Code;
var code = projectedCars.OrderedCars[i].Code;
if (!string.IsNullOrWhiteSpace(code))
{
enabledCarCodes.Add(code);
}
}
var alarmDtos = LoadDbAlarms(alarmService, enabledCarCodes);
var runtimeCars = RuntimeSnapshotProjection.ToRuntimeCars(cars, ResolveCarState);
var runtimeMaps = RuntimeSnapshotProjection.ToRuntimeMaps(cars);
var runtimeAlarms = RuntimeSnapshotProjection.ToRuntimeAlarms(
alarmDtos,
value => ResolveNamedValue(_alarmLevels, value),
@@ -516,8 +548,8 @@ internal sealed class MonitorService : IAsyncDisposable
{
_db = new DbSnapshot(
NextUpdateVersion(),
runtimeCars,
runtimeMaps,
projectedCars.Cars,
projectedCars.Maps,
runtimeAlarms,
now);
}
@@ -533,64 +565,30 @@ internal sealed class MonitorService : IAsyncDisposable
}
}
private RuntimeModel.Snapshot CreateIdleSnapshot() => new()
{
IsRunning = IsRunning,
Version = NextUpdateVersion(),
Time = Clock.Now.ToMillisecondString(),
Cars = [],
Maps = [],
Alarms = [],
DataSource = IdleSource,
IsDegraded = false,
DataAgeMs = 0
};
private RuntimeModel.Snapshot CreateRuntimeSnapshot(SnapshotPayload payload, string dataSource, bool isDegraded, long dataAgeMs)
private RuntimeModel.Snapshot CreateRuntimeSnapshot(
long version,
string time,
IReadOnlyList<RuntimeModel.Car> cars,
IReadOnlyList<RuntimeModel.Map> maps,
IReadOnlyList<RuntimeModel.Alarm> alarms,
string dataSource,
bool isDegraded,
long dataAgeMs)
{
return new RuntimeModel.Snapshot
{
IsRunning = IsRunning,
Version = payload.Version,
Time = payload.Time,
Cars = payload.Cars,
Maps = payload.Maps,
Alarms = payload.Alarms,
Version = version,
Time = time,
Cars = cars,
Maps = maps,
Alarms = alarms,
DataSource = dataSource,
IsDegraded = isDegraded,
DataAgeMs = dataAgeMs
};
}
private bool TryGetCachedNoLock(out RuntimeModel.Snapshot snapshot)
{
var cache = _snapshotCache;
if (cache.Snapshot is not null && Clock.UtcNow - cache.CachedAt <= _cacheInterval)
{
snapshot = cache.Snapshot;
return true;
}
snapshot = null!;
return false;
}
private bool TryTakePendingMemory(out RuntimeModel.Snapshot snapshot)
{
lock (_snapshotLock)
{
if (_mode.PendingMemorySnapshot is not null)
{
snapshot = _mode.PendingMemorySnapshot;
_mode = _mode with { PendingMemorySnapshot = null };
return true;
}
}
snapshot = null!;
return false;
}
private void RefreshRunState()
{
var shouldRun = Volatile.Read(ref _subscribers) > 0;
@@ -656,7 +654,7 @@ internal sealed class MonitorService : IAsyncDisposable
RuntimeMode.Database,
nextDatabaseRefreshAt: DateTime.MinValue);
_log.LogWarning("监控已切换为数据库降级模式,原因:{Reason}", NormalizeSwitchReason(reason));
_log.LogWarning("监控已切换为数据库降级模式,原因:{Reason}", string.IsNullOrWhiteSpace(reason) ? "实时服务不可用。" : reason);
}
private void ApplyRuntimeModeTransition(
@@ -712,22 +710,6 @@ internal sealed class MonitorService : IAsyncDisposable
_inFlight = null;
}
private static SnapshotPayload CreateSnapshotPayload(RuntimeModel.Snapshot snapshot) =>
new(
snapshot.Version,
string.IsNullOrWhiteSpace(snapshot.Time) ? Clock.Now.ToMillisecondString() : snapshot.Time,
snapshot.Cars,
snapshot.Maps,
snapshot.Alarms);
private static SnapshotPayload CreateSnapshotPayload(DbSnapshot snapshot) =>
new(
snapshot.Version,
snapshot.SnapshotAt == DateTime.MinValue ? Clock.Now.ToMillisecondString() : snapshot.SnapshotAt.ToMillisecondString(),
snapshot.Cars,
snapshot.Maps,
snapshot.Alarms);
private SnapshotBuildResult ApplyAlarmPresentationPolicy(RuntimeModel.Snapshot snapshot)
{
var incomingAlarms = snapshot.Alarms ?? [];
@@ -863,9 +845,6 @@ internal sealed class MonitorService : IAsyncDisposable
}
}
private static string NormalizeSwitchReason(string reason) =>
string.IsNullOrWhiteSpace(reason) ? "实时服务不可用。" : reason;
private static Dictionary<string, string> BuildDictNameMap(IReadOnlyList<DictItemDto> items, string dictCode)
{
if (items.Count == 0)
@@ -969,8 +948,10 @@ internal sealed class MonitorService : IAsyncDisposable
&& Math.Max(0, sourceSnapshot.Version) == cache.SourceVersion
&& alarmRevision == cache.AlarmRevision
&& string.Equals(sourceSnapshot.DataSource ?? string.Empty, cache.Snapshot.DataSource ?? string.Empty, StringComparison.Ordinal)
&& string.Equals(sourceSnapshot.Time ?? string.Empty, cache.Snapshot.Time ?? string.Empty, StringComparison.Ordinal)
&& sourceSnapshot.IsRunning == cache.Snapshot.IsRunning
&& sourceSnapshot.IsDegraded == cache.Snapshot.IsDegraded;
&& sourceSnapshot.IsDegraded == cache.Snapshot.IsDegraded
&& sourceSnapshot.DataAgeMs == cache.Snapshot.DataAgeMs;
}
private RuntimeMode GetRuntimeMode()
@@ -1036,8 +1017,9 @@ internal sealed class MonitorService : IAsyncDisposable
var sendTotal = Interlocked.Exchange(ref _sendMs, 0);
var dropped = Interlocked.Exchange(ref _dropped, 0);
var sent = Interlocked.Exchange(ref _sent, 0);
var sendFailed = Interlocked.Exchange(ref _sendFailed, 0);
if (updateCount == 0 && sendCount == 0 && dropped == 0 && sent == 0)
if (updateCount == 0 && sendCount == 0 && dropped == 0 && sent == 0 && sendFailed == 0)
{
return;
}
@@ -1045,13 +1027,14 @@ internal sealed class MonitorService : IAsyncDisposable
var updateAvg = updateCount == 0 ? 0 : Math.Round((double)updateTotal / updateCount, 2);
var sendAvg = sendCount == 0 ? 0 : Math.Round((double)sendTotal / sendCount, 2);
_log.LogInformation(
"监控心跳:间隔[{HeartDueTime}ms] 更新均耗[{UpdateAvg}ms] 更新样本[{UpdateCount}] 推送均耗[{SendAvg}ms] 推送样本[{SendCount}] 已推送[{Sent}] 丢帧[{Dropped}]。",
"监控心跳:间隔[{HeartDueTime}ms] 更新均耗[{UpdateAvg}ms] 更新样本[{UpdateCount}] 推送均耗[{SendAvg}ms] 推送样本[{SendCount}] 已推送[{Sent}] 推送失败[{SendFailed}] 丢帧[{Dropped}]。",
(int)_probeInterval.TotalMilliseconds,
updateAvg,
updateCount,
sendAvg,
sendCount,
sent,
sendFailed,
dropped);
}
}
@@ -7,7 +7,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<CETCompat>false</CETCompat>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<AssemblyName>FASS.Simulator</AssemblyName>
<ApplicationIcon>Assets\App.ico</ApplicationIcon>
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration</InterceptorsNamespaces>
@@ -15,11 +15,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.WindowsDesktop" Version="2.4.2" />
<PackageReference Include="Common.WindowsDesktop" Version="2.4.3" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.10" />
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.1" />
@@ -1,3 +1,4 @@
using Common.NETCore.Timing;
using Common.WindowsDesktop.Consts;
using FASS.Simulator.Collections;
using System.Collections.Concurrent;
@@ -237,7 +238,7 @@ namespace FASS.Simulator.Models.Message
}
var pending = Math.Max(0, Volatile.Read(ref _pendingCount));
var notice = $"[ {DateTime.Now.ToString(TimestampFormat)} ]=>[ {_resumeNoticeLabel}:暂停期间丢弃 {dropped} 条日志,待显示日志 {pending} 条 ]";
var notice = $"[ {Clock.Now.ToString(TimestampFormat)} ]=>[ {_resumeNoticeLabel}:暂停期间丢弃 {dropped} 条日志,待显示日志 {pending} 条 ]";
if (_resumeNoticeIncludeDisplayedStats)
{
var displayed = _dispatcher.CheckAccess() ? _target.Count : _dispatcher.Invoke(() => _target.Count);
@@ -1,3 +1,5 @@
using Common.NETCore.Timing;
namespace FASS.Simulator.Models
{
public enum SimulatorSessionType
@@ -43,7 +45,7 @@ namespace FASS.Simulator.Models
public SimulatorRuntimeSnapshot GetSnapshot()
{
var nowTicks = DateTime.UtcNow.Ticks;
var nowTicks = Clock.UtcNow.Ticks;
var nextRefreshUtcTicks = Volatile.Read(ref _nextRefreshUtcTicks);
if (nowTicks < nextRefreshUtcTicks)
{
@@ -1,3 +1,4 @@
using Common.NETCore.Timing;
using Quartz;
namespace FASS.Simulator.Services.CronTasks.Jobs
@@ -14,7 +15,7 @@ namespace FASS.Simulator.Services.CronTasks.Jobs
public Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("当前时间:[{DateTimeNow}]", DateTime.Now);
Logger.LogInformation("当前时间:[{DateTimeNow}]", Clock.Now);
return Task.CompletedTask;
}
@@ -1,3 +1,4 @@
using Common.NETCore.Timing;
using System.Collections.Concurrent;
namespace FASS.Simulator.Services.Sessions
@@ -85,7 +86,7 @@ namespace FASS.Simulator.Services.Sessions
var interval = NormalizeTick(_tickProvider());
await Task.Delay(interval, cancellationToken);
var utcNow = DateTime.UtcNow;
var utcNow = Clock.UtcNow;
foreach (var session in worker.Sessions.Keys)
{
if (session.SessionIsClosed)
@@ -1,3 +1,4 @@
using Common.NETCore.Timing;
using Common.WindowsDesktop.Consts;
using Common.WindowsDesktop.Enums;
using FASS.Simulator.Models.Message;
@@ -117,7 +118,7 @@ namespace FASS.Simulator.Utility
private static Task AddMessageInternal(string message, Brush brush, Action<MessageItem> action, bool includeTimestamp)
{
var text = includeTimestamp ? $"[ {DateTime.Now.ToString(TimestampFormat)} ]=>[ {message} ]" : message;
var text = includeTimestamp ? $"[ {Clock.Now.ToString(TimestampFormat)} ]=>[ {message} ]" : message;
action(new MessageItem(text, brush));
return Task.CompletedTask;
}
@@ -1,5 +1,6 @@
using Common.NETCore.Extensions;
using Common.NETCore.Helpers;
using Common.NETCore.Timing;
using CommunityToolkit.Mvvm.ComponentModel;
using FASS.Extend.Car.Fairyland.Pcb;
using FASS.Simulator.Models;
@@ -82,8 +83,8 @@ namespace FASS.Simulator.ViewModels.Pages.Car.Fairyland.Pcb
FullMode = BoundedChannelFullMode.DropWrite,
AllowSynchronousContinuations = false
});
_nextKeepaliveUtc = DateTime.UtcNow;
_nextReconnectUtc = DateTime.UtcNow;
_nextKeepaliveUtc = Clock.UtcNow;
_nextReconnectUtc = Clock.UtcNow;
}
public int SessionShardKey => Model?.Car ?? 0;
@@ -635,13 +636,13 @@ namespace FASS.Simulator.ViewModels.Pages.Car.Fairyland.Pcb
if (command.Command != SessionCommand.Receive)
{
HandleCommand(command, DateTime.UtcNow);
HandleCommand(command, Clock.UtcNow);
return true;
}
var dropped = Interlocked.Increment(ref _commandDropCount);
_runtimeTuning.RecordCommandDrop(SimulatorSessionType.Pcb);
var nowUtcTicks = DateTime.UtcNow.Ticks;
var nowUtcTicks = Clock.UtcNow.Ticks;
var intervalTicks = TimeSpan.FromMilliseconds(_runtimeTuning.GetSnapshot().CommandDropLogIntervalMilliseconds).Ticks;
var lastUtcTicks = Volatile.Read(ref _lastCommandDropLogUtcTicks);
if (nowUtcTicks - lastUtcTicks >= intervalTicks &&
@@ -1,5 +1,6 @@
using Common.NETCore.Extensions;
using Common.NETCore.Helpers;
using Common.NETCore.Timing;
using CommunityToolkit.Mvvm.ComponentModel;
using FASS.Extend.Car.Fairyland.Plc;
using FASS.Simulator.Models;
@@ -80,7 +81,7 @@ namespace FASS.Simulator.ViewModels.Pages.Car.Fairyland.Plc
FullMode = BoundedChannelFullMode.DropWrite,
AllowSynchronousContinuations = false
});
_nextReconnectUtc = DateTime.UtcNow;
_nextReconnectUtc = Clock.UtcNow;
}
public int SessionShardKey => Model?.Car ?? 0;
@@ -630,13 +631,13 @@ namespace FASS.Simulator.ViewModels.Pages.Car.Fairyland.Plc
if (command.Command != SessionCommand.Receive)
{
HandleCommand(command, DateTime.UtcNow);
HandleCommand(command, Clock.UtcNow);
return true;
}
var dropped = Interlocked.Increment(ref _commandDropCount);
_runtimeTuning.RecordCommandDrop(SimulatorSessionType.Plc);
var nowUtcTicks = DateTime.UtcNow.Ticks;
var nowUtcTicks = Clock.UtcNow.Ticks;
var intervalTicks = TimeSpan.FromMilliseconds(_runtimeTuning.GetSnapshot().CommandDropLogIntervalMilliseconds).Ticks;
var lastUtcTicks = Volatile.Read(ref _lastCommandDropLogUtcTicks);
if (nowUtcTicks - lastUtcTicks >= intervalTicks &&
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CETCompat>false</CETCompat>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<AssemblyName>FASS.Api</AssemblyName>
<ApplicationIcon>Resources\App.ico</ApplicationIcon>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
@@ -13,12 +13,12 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.80.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="MiniExcel" Version="1.44.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
<PackageReference Include="MiniExcel" Version="1.45.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="SkiaSharp" Version="3.119.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.1" />
<PackageReference Include="SkiaSharp" Version="4.150.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>
<ItemGroup>
@@ -671,6 +671,16 @@
return typeof mapItem === 'function' ? items.map(mapItem) : items;
}
function updateTableRowCells(row, item, selectors) {
const columnSelectors = Array.isArray(selectors) ? selectors : [];
for (let i = 0; i < columnSelectors.length; i++) {
page.updateTableCell(row, i, columnSelectors[i](item));
}
while (row.children.length > columnSelectors.length) {
row.removeChild(row.lastChild);
}
}
function cloneCar(source) {
return {
code: String(source?.code ?? ''),
@@ -856,7 +866,7 @@
}
function updateCarRow(row, car) {
page.updateTableRowCells(row, car, carColumnSelectors);
updateTableRowCells(row, car, carColumnSelectors);
}
function renderCars() {
@@ -916,7 +926,7 @@
}
function updateAlarmRow(row, alarm) {
page.updateTableRowCells(row, alarm, alarmColumnSelectors);
updateTableRowCells(row, alarm, alarmColumnSelectors);
}
function renderAlarms() {
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CETCompat>false</CETCompat>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<AssemblyName>FASS.Mvc</AssemblyName>
<ApplicationIcon>Resources\App.ico</ApplicationIcon>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
@@ -13,10 +13,10 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.80.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageReference Include="MiniExcel" Version="1.44.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
<PackageReference Include="MiniExcel" Version="1.45.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="SkiaSharp" Version="3.119.4" />
<PackageReference Include="SkiaSharp" Version="4.150.1" />
</ItemGroup>
<ItemGroup>
@@ -744,6 +744,7 @@ button:active {
width: 100%;
height: 100%;
display: block;
touch-action: none;
flex: 1 1 auto;
min-height: 0;
background: var(--canvas-bg);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
View File
View File
View File
View File
View File
@@ -0,0 +1,77 @@
using Common.AspNetCore.Extensions;
using Common.AspNetCore.Helpers;
using Common.Frame.Dtos.Trace;
using Common.Frame.Services.Trace.Interfaces;
using Common.NETCore.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
namespace FASS.Scheduler.Attributes
{
public class ActionLogIgnoreAttribute : ActionFilterAttribute
{
}
public class ActionLogAttribute : ActionFilterAttribute
{
private readonly IUserActionService _userLogService;
private readonly ILogger<ActionLogAttribute> _logger;
public ActionLogAttribute(
IUserActionService userLogService,
ILogger<ActionLogAttribute> logger)
{
_userLogService = userLogService;
_logger = logger;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (IsIgnore(context))
{
await next();
return;
}
var watch = Stopwatch.StartNew();
try
{
await next();
}
finally
{
watch.Stop();
var userActionDto = new UserActionDto
{
UserId = IdentityHelper.ToUserIdentity(context.HttpContext.User).Id,
Controller = context.RouteData.DataTokens["area"] is null ? $"{context.RouteData.Values["controller"]}" : $"{context.RouteData.DataTokens["area"]}/{context.RouteData.Values["controller"]}",
Action = $"{context.RouteData.Values["action"]}",
Watch = watch.Elapsed.ToString(),
RequestUrl = context.HttpContext.Request.GetAbsoluteUri(),
RequestToken = context.HttpContext.Request.Cookies["Authorization"],
ResponseCode = context.HttpContext.Response.StatusCode.ToString(),
UserAgent = context.HttpContext.Request.Headers["User-Agent"],
IpAddress = context.HttpContext.GetUserIp()
};
try
{
await _userLogService.AddAsync(userActionDto);
}
catch (Exception ex)
{
_logger.LogError(ex, "记录用户操作日志失败");
}
}
}
private static bool IsIgnore(FilterContext context)
{
if (context.Filters.OfType<ActionLogIgnoreAttribute>().Any())
{
return true;
}
return context.ActionDescriptor.FilterDescriptors.Select(f => f.Filter).OfType<TypeFilterAttribute>().Any(f => f.ImplementationType == typeof(ActionLogIgnoreAttribute));
}
}
}
@@ -0,0 +1,56 @@
using Common.AspNetCore.Helpers;
using Common.Frame.Services.Account.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FASS.Scheduler.Attributes
{
[AttributeUsage(AttributeTargets.All)]
public class AuthorizeActionIgnoreAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
}
}
[AttributeUsage(AttributeTargets.All)]
public class AuthorizeActionAttribute : Attribute, IAsyncAuthorizationFilter
{
private readonly IPermissionService _permissionService;
public AuthorizeActionAttribute(
IPermissionService permissionService)
{
_permissionService = permissionService;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (IsIgnore(context))
{
return;
}
var userIdentity = IdentityHelper.ToUserIdentity(context.HttpContext.User);
if (userIdentity.IsSystem)
{
return;
}
var target = context.HttpContext.Request.Path.ToString();
var isOk = await _permissionService.CheckTargetAsync(userIdentity.Id, target);
if (isOk)
{
return;
}
context.Result = new UnauthorizedResult();
}
private static bool IsIgnore(AuthorizationFilterContext context)
{
if (context.Filters.OfType<AuthorizeActionIgnoreAttribute>().Any())
{
return true;
}
return context.ActionDescriptor.FilterDescriptors.Select(f => f.Filter).OfType<TypeFilterAttribute>().Any(f => f.ImplementationType == typeof(AuthorizeActionIgnoreAttribute));
}
}
}
@@ -0,0 +1,46 @@
using Common.NETCore.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Net;
namespace FASS.Scheduler.Attributes
{
public class ResultAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is StatusCodeResult statusCodeResult)
{
var responseResult = new ResponseResult();
responseResult.Code = statusCodeResult.StatusCode.ToString();
if (statusCodeResult is OkResult)
{
responseResult.Success = true;
responseResult.Data = Enum.GetName(typeof(HttpStatusCode), (HttpStatusCode)statusCodeResult.StatusCode);
}
else
{
responseResult.Success = false;
responseResult.Message = Enum.GetName(typeof(HttpStatusCode), (HttpStatusCode)statusCodeResult.StatusCode);
}
context.Result = new OkObjectResult(responseResult);
}
else if (context.Result is ObjectResult objectResult)
{
var responseResult = new ResponseResult();
responseResult.Code = (objectResult?.StatusCode ?? 0).ToString();
if (objectResult is OkObjectResult)
{
responseResult.Success = true;
responseResult.Data = objectResult.Value;
}
else
{
responseResult.Success = false;
responseResult.Message = objectResult?.Value;
}
context.Result = new OkObjectResult(responseResult);
}
}
}
}
@@ -0,0 +1,16 @@
using FASS.Scheduler.Attributes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace FASS.Scheduler.Controllers.Base
{
[Authorize]
[ApiController]
[Route("api/v1/[controller]/[action]")]
[TypeFilter(typeof(AuthorizeActionAttribute))]
[TypeFilter(typeof(ActionLogAttribute))]
public class BaseController : ControllerBase
{
}
}
@@ -0,0 +1,33 @@
using FASS.Scheduler.Attributes;
using FASS.Scheduler.Controllers.Base;
using FASS.Service.Consts.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
namespace FASS.Scheduler.Controllers
{
[AllowAnonymous]
[TypeFilter(typeof(AuthorizeActionIgnoreAttribute))]
[TypeFilter(typeof(ActionLogIgnoreAttribute))]
[Tags("接口")]
[EnableRateLimiting(AppConst.Rate.Name)]
public class CarController : BaseController
{
private readonly ILogger<CarController> _logger;
public CarController(
ILogger<CarController> logger)
{
_logger = logger;
}
[Tags("状态")]
[HttpPost]
[DisableRateLimiting]
public IActionResult State()
{
return Ok("Ok");
}
}
}
@@ -0,0 +1,65 @@
using FASS.Scheduler.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace FASS.Scheduler.Extensions.Configure
{
public static class AuthExtension
{
public static IServiceCollection AddAuth(this IServiceCollection services, AppSettings appSettings)
{
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = appSettings.Auth.Issuer,
ValidateAudience = true,
ValidAudience = appSettings.Auth.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings.Auth.SigningKey)),
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
if (context.Request.Headers.ContainsKey("Authorization"))
{
context.Token = context.Request.Headers["Authorization"].FirstOrDefault()?.Substring("Bearer ".Length);
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
if (context.Exception is SecurityTokenExpiredException)
{
context.Response.Headers.Append("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
return services;
}
public static IApplicationBuilder UseAuth(this IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
return app;
}
}
}
@@ -0,0 +1,30 @@
using FASS.Scheduler.Models;
using FASS.Scheduler.Services.CronTasks;
using FASS.Scheduler.Services.EventBus;
using FASS.Scheduler.Services.Extends;
using FASS.Service.Extensions;
namespace FASS.Scheduler.Extensions.Configure
{
public static class BootExtension
{
public static IServiceCollection AddBoot(this IServiceCollection services, IConfiguration configuration, AppSettings appSettings)
{
services.AddSingleton<ExtendService>();
services.AddSingleton<CronTaskService>();
services.AddSingleton<EventBusService>();
services.AddService(configuration, appSettings.App.ActivationCode, () => appSettings.Frame);
return services;
}
public static IServiceProvider UseBoot(this IServiceProvider provider)
{
provider.UseService();
return provider;
}
}
}
@@ -0,0 +1,44 @@
using Common.NETCore.Extensions;
using Common.NETCore.Models;
using Microsoft.AspNetCore.Diagnostics;
using System.Text.Json;
namespace FASS.Scheduler.Extensions.Configure
{
public static class ExceptionExtension
{
public static IApplicationBuilder UseException(this IApplicationBuilder app)
{
var jsonSerializerOptions = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
};
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error.GetBaseException();
var responseResult = new ResponseResult()
{
Success = false,
Code = context.Response.StatusCode.ToString()
};
if (ex != null)
{
responseResult.Message = ex.Message;
}
else
{
responseResult.Message = "未知错误";
}
context.Response.StatusCode = StatusCodes.Status200OK;
context.Response.ContentType = "application/json";
await context.Response.Body.WriteAsync(responseResult.ToJson(jsonSerializerOptions).ToBytes());
});
});
return app;
}
}
}
@@ -0,0 +1,12 @@
using Common.AspNetCore.Extensions;
namespace FASS.Scheduler.Extensions.Configure
{
public static class SessionExtension
{
public static IApplicationBuilder UseCurrent(this IApplicationBuilder app)
{
return app.UseCurrentUserContext();
}
}
}
@@ -0,0 +1,45 @@
using Common.NETCore.Utility;
using Microsoft.OpenApi;
namespace FASS.Scheduler.Extensions.Configure
{
public static class SwaggerExtension
{
public static IServiceCollection AddSwashbuckle(this IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = Session.AssemblyName.Name,
Version = Session.AssemblyName.Version?.ToString()
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Name = "Authorization",
BearerFormat = "JWT",
Description = "Value {Bearer Token}"
});
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("Bearer", document)] = []
});
options.OrderActionsBy(api => api.RelativePath);
//options.TagActionsBy(api => [api.HttpMethod]);
});
return services;
}
public static IApplicationBuilder UseSwashbuckle(this IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
});
return app;
}
}
}
@@ -0,0 +1,29 @@
using Common.AspNetCore.Helpers;
using FASS.Scheduler.Models;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
namespace FASS.Scheduler.Extensions
{
public static class TokenExtension
{
public static string GetToken(this AppSettings appSettings, IEnumerable<Claim> claims)
{
var signingKey = appSettings.Auth.SigningKey;
var algorithm = SecurityAlgorithms.HmacSha256;
var issuer = appSettings.Auth.Issuer;
var audience = appSettings.Auth.Audience;
var notBefore = DateTime.Now;
var expires = notBefore.AddSeconds(appSettings.Auth.ExpireSeconds);
var token = JwtHelper.CreateToken(signingKey, algorithm, issuer, audience, claims, notBefore, expires);
return token;
}
public static string RefreshToken(this AppSettings appSettings, string token)
{
var signingKey = appSettings.Auth.SigningKey;
var refreshToken = JwtHelper.RefreshToken(token, signingKey);
return refreshToken;
}
}
}
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CETCompat>false</CETCompat>
<Version>2.4.3</Version>
<AssemblyName>FASS.Scheduler</AssemblyName>
<ApplicationIcon>Resources\App.ico</ApplicationIcon>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Net" Version="2.4.3" />
<PackageReference Include="Grpc.AspNetCore" Version="2.80.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FASS.Service.Lite\FASS.Service.Lite.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\remote.proto" GrpcServices="Server" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
@FASS.Scheduler.Lite_HostAddress = http://localhost:5276
GET {{FASS.Scheduler.Lite_HostAddress}}/weatherforecast/
Accept: application/json
###
@@ -0,0 +1,53 @@
using FASS.Scheduler.Grpc;
using Grpc.Core;
namespace FASS.Scheduler.Grpcs;
public sealed class RemoteService : Remote.RemoteBase
{
private readonly ILogger<RemoteService> _logger;
public RemoteService(ILogger<RemoteService> logger)
{
_logger = logger;
}
private async Task<ResponseReply> TryExecuteResponseAsync(
string operation,
Func<CancellationToken, Task<ResponseReply>> action,
CancellationToken cancellationToken = default)
{
try
{
return await action(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "远程调用执行失败:操作[{Operation}]。", operation);
return new ResponseReply
{
Success = false,
Message = ex.Message
};
}
}
private Task<ResponseReply> Execute(
string operation,
Action action,
CancellationToken cancellationToken = default) =>
TryExecuteResponseAsync(
operation,
_ =>
{
action();
return Task.FromResult(new ResponseReply { Success = true });
},
cancellationToken);
public override Task<ResponseReply> CarAdd(CarAddRequest request, ServerCallContext context) =>
Execute(nameof(CarAdd), () =>
{
_logger.LogDebug("成功:{RequestDto}", request.Dto);
}, context.CancellationToken);
}
@@ -0,0 +1,56 @@
using Common.Frame.Options;
namespace FASS.Scheduler.Models
{
public class AppSettings
{
public Rate Rate { get; set; } = null!;
public Auth Auth { get; set; } = null!;
public App App { get; set; } = null!;
public FrameOption Frame { get; set; } = null!;
public Scheduler Scheduler { get; set; } = null!;
public Extend Extend { get; set; } = null!;
}
public class Rate
{
public int PermitLimit { get; set; }
public int QueueLimit { get; set; }
public int WindowMilliseconds { get; set; }
}
public class Auth
{
public required string SigningKey { get; set; }
public required string Issuer { get; set; }
public required string Audience { get; set; }
public int ExpireSeconds { get; set; }
}
public class App
{
public required string ActivationCode { get; set; }
}
public class Scheduler
{
public int StartupDueTime { get; set; }
}
public class Service
{
public string? TcpServerLocalIP { get; set; }
public string? UdpServerLocalIP { get; set; }
}
public class Extend
{
public bool EnableComClient { get; set; }
public string? ComClientPortName { get; set; }
public bool EnableTcpClient { get; set; }
public string? TcpClientRemoteIP { get; set; }
public bool EnableTcpServer { get; set; }
public string? TcpServerLocalIP { get; set; }
public bool EnableUdpServer { get; set; }
public string? UdpServerRemoteIP { get; set; }
public string? UdpServerLocalIP { get; set; }
public bool EnableHttpClient { get; set; }
public string? HttpClientBaseAddress { get; set; }
public bool EnableHttpServer { get; set; }
public List<string> HttpServerPrefixes { get; set; } = [];
}
}
@@ -0,0 +1,68 @@
using Common.NETCore;
using Common.NETCore.Extensions;
using Common.NETCore.Helpers;
using FASS.Scheduler.Attributes;
using FASS.Scheduler.Extensions.Configure;
using FASS.Scheduler.Grpcs;
using FASS.Scheduler.Models;
using FASS.Scheduler.Services;
using FASS.Service.Consts.Core;
using Microsoft.AspNetCore.RateLimiting;
using Serilog;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
var appSettings = builder.Configuration.Get<AppSettings>();
builder.Services.AddSingleton(Guard.NotNull(appSettings));
builder.Services.AddSerilog((services, logger) => logger.ReadFrom.Configuration(builder.Configuration));
builder.Services
.AddControllers(options =>
{
options.Filters.Add(typeof(ResultAttribute));
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.AddDefaultOptions();
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddSignalR();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwashbuckle();
builder.Services.AddAuth(appSettings);
builder.Services.AddBoot(builder.Configuration, appSettings);
builder.Services.AddHostedService<AppHostService>();
builder.Services.AddOpenApi();
builder.Services.AddGrpc();
builder.Services.AddCors(options =>
{
options.AddPolicy(AppConst.Cors.Name, policy =>
{
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().WithExposedHeaders("X-Pagination");
});
});
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(AppConst.Rate.Name, opt =>
{
opt.Window = TimeSpan.FromMilliseconds(appSettings.Rate.WindowMilliseconds);
opt.PermitLimit = appSettings.Rate.PermitLimit;
opt.QueueLimit = appSettings.Rate.QueueLimit;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
var app = builder.Build();
app.UseException();
app.UseSerilogRequestLogging();
app.UseRouting();
app.UseSwashbuckle();
app.UseAuth();
app.UseCurrent();
app.Services.UseBoot();
app.MapGrpcService<RemoteService>();
app.UseCors(AppConst.Cors.Name);
app.UseRateLimiter();
app.MapControllers();
app.Lifetime.ApplicationStarted.Register(() => BrowserHelper.OpenBrowser($"{app.Urls.First()}/swagger"));
app.Run();
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:20101",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,19 @@
syntax = "proto3";
option csharp_namespace = "FASS.Scheduler.Grpc";
package remote;
service Remote {
rpc CarAdd (CarAddRequest) returns (ResponseReply);
}
message ResponseReply {
bool success = 1;
string message = 2;
string data = 3;
}
message CarAddRequest {
string dto = 1;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1,148 @@
using Common.AspNetCore.Extensions;
using Common.Frame.Services.Cache.Interfaces;
using Common.NETCore.Utility;
using FASS.Scheduler.Models;
using FASS.Scheduler.Services.CronTasks;
using FASS.Scheduler.Services.EventBus;
using FASS.Scheduler.Services.Extends;
using FASS.Scheduler.Utility;
using FASS.Service.Dtos.Setting;
namespace FASS.Scheduler.Services;
public class AppHostService : IHostedService, IAsyncDisposable
{
private IHostApplicationLifetime Lifetime { get; }
private CancellationTokenSource? _startupTokenSource;
private Task? _startupTask;
public ILogger<AppHostService> Logger { get; }
public AppSettings AppSettings { get; }
public IServiceProvider ServiceProvider { get; }
public ExtendService ExtendService { get; private set; } = null!;
public EventBusService EventBusService { get; private set; } = null!;
public CronTaskService CronTaskService { get; private set; } = null!;
public AppHostService(
IHostApplicationLifetime lifetime,
ILogger<AppHostService> logger,
AppSettings appSettings,
IServiceProvider serviceProvider)
{
Lifetime = lifetime;
Logger = logger;
AppSettings = appSettings;
ServiceProvider = serviceProvider;
Lifetime.ApplicationStarted.Register(OnApplicationStarted);
}
public Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("服务启动中");
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("服务停止中");
var startupTokenSource = Interlocked.Exchange(ref _startupTokenSource, null);
startupTokenSource?.Cancel();
var startupTask = Interlocked.Exchange(ref _startupTask, null);
if (startupTask is not null)
{
try
{
await startupTask.WaitAsync(cancellationToken);
}
catch (OperationCanceledException) when (startupTokenSource?.IsCancellationRequested == true || cancellationToken.IsCancellationRequested)
{
Logger.LogInformation("服务启动流程已取消");
}
catch (Exception ex)
{
Logger.LogError(ex, "服务等待启动流程结束失败");
}
}
startupTokenSource?.Dispose();
var stopTasks = new List<Task>();
if (CronTaskService is not null)
{
stopTasks.Add(CronTaskService.StopAsync(cancellationToken));
}
if (EventBusService is not null)
{
stopTasks.Add(EventBusService.StopAsync(cancellationToken));
}
if (ExtendService is not null)
{
stopTasks.Add(ExtendService.StopAsync(cancellationToken));
}
await Task.WhenAll(stopTasks);
}
public async ValueTask DisposeAsync()
{
Logger.LogInformation("服务释放资源");
}
private void OnApplicationStarted()
{
if (_startupTask is not null)
{
return;
}
_startupTokenSource = new CancellationTokenSource();
_startupTask = RunStartupAsync(_startupTokenSource.Token);
}
private async Task RunStartupAsync(CancellationToken cancellationToken)
{
try
{
Logger.LogInformation("[{Name} V {Version}]", Session.AssemblyName.Name, Session.AssemblyName.Version);
if (AppSettings.Scheduler.StartupDueTime > 0)
{
Logger.LogInformation("启动延迟:{Delay} 毫秒", AppSettings.Scheduler.StartupDueTime);
await Task.Delay(AppSettings.Scheduler.StartupDueTime, cancellationToken);
}
Logger.LogInformation("--------初始化--------");
InitializeCache();
InitializeService();
Logger.LogInformation("--------启动--------");
await Task.WhenAll(
ExtendService.StartAsync(cancellationToken),
EventBusService.StartAsync(cancellationToken),
CronTaskService.StartAsync(cancellationToken));
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
Logger.LogInformation("--------取消--------");
}
catch (Exception ex)
{
Logger.LogCritical(ex, "错误");
}
}
public void InitializeCache()
{
ServiceProvider.GetScopeService<IDataService>().GetConfigToDto<ConfigServiceDto>(CacheKey.Setting.ConfigService);
}
public void InitializeService()
{
ExtendService = ServiceProvider.GetRequiredService<ExtendService>();
EventBusService = ServiceProvider.GetRequiredService<EventBusService>();
CronTaskService = ServiceProvider.GetRequiredService<CronTaskService>();
}
}
@@ -0,0 +1,91 @@
using FASS.Scheduler.Models;
using FASS.Scheduler.Services.CronTasks.Jobs;
using Quartz;
namespace FASS.Scheduler.Services.CronTasks
{
public class CronTaskService
{
public ILogger<CronTaskService> Logger { get; }
public AppSettings AppSettings { get; }
public IServiceProvider ServiceProvider { get; }
private IScheduler? _scheduler;
private static readonly JobKey DefaultJobKey = new("defaultJob", "defaultGroup");
private static readonly TriggerKey DefaultTriggerKey = new("defaultTrigger", "defaultGroup");
public CronTaskService(
ILogger<CronTaskService> logger,
AppSettings appSettings,
IServiceProvider serviceProvider)
{
Logger = logger;
AppSettings = appSettings;
ServiceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!AppSettings.Frame.CronTask.IsEnable)
{
Logger.LogInformation("定时任务未启用");
return;
}
try
{
var factory = ServiceProvider.GetRequiredService<ISchedulerFactory>();
_scheduler ??= await factory.GetScheduler(cancellationToken);
if (!_scheduler.IsStarted)
{
await _scheduler.Start(cancellationToken);
}
if (!await _scheduler.CheckExists(DefaultJobKey, cancellationToken))
{
var job = JobBuilder.Create<DefaultJob>()
.WithIdentity(DefaultJobKey)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity(DefaultTriggerKey)
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever())
.Build();
await _scheduler.ScheduleJob(job, trigger, cancellationToken);
}
Logger.LogInformation("定时任务已启动");
}
catch (Exception ex)
{
Logger.LogError(ex, "错误");
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (!AppSettings.Frame.CronTask.IsEnable)
{
Logger.LogInformation("定时任务未启用");
return;
}
try
{
if (_scheduler is not null && !_scheduler.IsShutdown)
{
await _scheduler.Shutdown(cancellationToken);
}
Logger.LogInformation("定时任务已停止");
}
catch (Exception ex)
{
Logger.LogError(ex, "错误");
}
}
}
}
@@ -0,0 +1,22 @@
using Quartz;
namespace FASS.Scheduler.Services.CronTasks.Jobs
{
public class DefaultJob : IJob
{
public ILogger<DefaultJob> Logger { get; }
public DefaultJob(
ILogger<DefaultJob> logger)
{
Logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("当前时间:[{DateTimeNow}]", DateTime.Now);
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,61 @@
using DotNetCore.CAP;
using FASS.Scheduler.Models;
using FASS.Scheduler.Services.EventBus.Subscribes;
namespace FASS.Scheduler.Services.EventBus
{
public class EventBusService : ICapSubscribe
{
public ILogger<EventBusService> Logger { get; }
public AppSettings AppSettings { get; }
public IServiceProvider ServiceProvider { get; }
public DefaultSubscribe DefaultSubscribe { get; } = null!;
public EventBusService(
ILogger<EventBusService> logger,
AppSettings appSettings,
IServiceProvider serviceProvider)
{
Logger = logger;
AppSettings = appSettings;
ServiceProvider = serviceProvider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
if (!AppSettings.Frame.EventBus.IsEnable)
{
Logger.LogInformation("事件总线未启用");
return Task.CompletedTask;
}
try
{
Logger.LogInformation("事件总线已启动");
}
catch (Exception ex)
{
Logger.LogError(ex, "错误");
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
if (!AppSettings.Frame.EventBus.IsEnable)
{
Logger.LogInformation("事件总线未启用");
return Task.CompletedTask;
}
try
{
Logger.LogInformation("事件总线已停止");
}
catch (Exception ex)
{
Logger.LogError(ex, "错误");
}
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,27 @@
using DotNetCore.CAP;
namespace FASS.Scheduler.Services.EventBus.Subscribes
{
public class DefaultSubscribe : ICapSubscribe
{
public EventBusService EventBusService { get; }
public DefaultSubscribe(
EventBusService eventBusService)
{
EventBusService = eventBusService;
}
[CapSubscribe("CarController.Lock")]
public void CarControllerLock(string json)
{
}
[CapSubscribe("CarController.UnLock")]
public void CarControllerUnLock(string json)
{
}
}
}
@@ -0,0 +1,123 @@
using Common.NETCore;
using Common.NETCore.Helpers;
using ComClient = Common.Net.Com.ComClient;
namespace FASS.Scheduler.Services.Extends.Demo
{
public class ExtendComClientService
{
public ExtendService ExtendService { get; }
public bool IsRunning { get; private set; }
private readonly ComClient _comClient;
public ExtendComClientService(
ExtendService extendService)
{
ExtendService = extendService;
var comClientPortName = Guard.NotNull(ExtendService.AppSettings.Extend.ComClientPortName);
_comClient = new ComClient() { PortName = comClientPortName };
_comClient.Opened += ComClient_Opened;
_comClient.Closed += ComClient_Closed;
_comClient.Writed += ComClient_Writed;
_comClient.Readed += ComClient_Readed;
}
public void Start()
{
try
{
if (IsRunning)
{
return;
}
IsRunning = true;
_comClient.OpenAndRead();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public void Stop()
{
try
{
if (!IsRunning)
{
return;
}
IsRunning = false;
_comClient.Close();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
private void ComClient_Opened(ComClient client)
{
Task.Run(() => KeepaliveAsync(client));
ExtendService.Logger.LogInformation("串口客户端已打开:端口[{PortName}] 波特率[{BaudRate}]。", client.Client.PortName, client.Client.BaudRate);
}
private void ComClient_Closed(ComClient client)
{
Task.Run(() => ReconnectAsync(client));
ExtendService.Logger.LogInformation("串口客户端已关闭。");
}
private void ComClient_Writed(ComClient client, byte[] data)
{
ExtendService.Logger.LogInformation("串口客户端已发送:端口[{PortName}] 波特率[{BaudRate}] 数据[{Data}]。", client.Client.PortName, client.Client.BaudRate, ByteHelper.ByteArrayToHexString(data));
}
private void ComClient_Readed(ComClient client, byte[] data)
{
ExtendService.Logger.LogInformation("串口客户端已接收:端口[{PortName}] 波特率[{BaudRate}] 数据[{Data}]。", client.Client.PortName, client.Client.BaudRate, ByteHelper.ByteArrayToHexString(data));
_comClient.Write(data);
}
private async Task KeepaliveAsync(ComClient client)
{
while (IsRunning && client.IsOpen)
{
try
{
byte[] sendByteArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
client.Write(sendByteArray);
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(500);
}
}
}
private async Task ReconnectAsync(ComClient client)
{
while (IsRunning && !client.IsOpen)
{
try
{
client.OpenAndRead();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(5000);
}
}
}
}
}
@@ -0,0 +1,96 @@
using Common.NETCore;
using HttpClient = Common.Net.Http.HttpClient;
namespace FASS.Scheduler.Services.Extends.Demo
{
public class ExtendHttpClientService
{
public ExtendService ExtendService { get; }
public bool IsRunning { get; private set; }
private readonly HttpClient _httpClient;
public ExtendHttpClientService(
ExtendService extendService)
{
ExtendService = extendService;
var httpClientBaseAddress = Guard.NotNull(ExtendService.AppSettings.Extend.HttpClientBaseAddress);
_httpClient = new HttpClient() { BaseAddress = new Uri(httpClientBaseAddress) };
}
public void Start()
{
try
{
if (IsRunning)
{
return;
}
IsRunning = true;
Task.Run(Keepalive);
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public void Stop()
{
try
{
if (!IsRunning)
{
return;
}
IsRunning = false;
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public async Task HttpGetAsync(object? param = null)
{
var response = await _httpClient.GetAsTextAsync("/test/get", param);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
ExtendService.Logger.LogInformation("HTTP GET 响应:[{Result}]。", result);
}
public async Task HttpPostAsync(object? param = null)
{
var response = await _httpClient.PostAsTextAsync("/test/post", param);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
ExtendService.Logger.LogInformation("HTTP POST 响应:[{Result}]。", result);
}
private async Task Keepalive()
{
while (true)
{
if (!IsRunning)
{
break;
}
try
{
var message = "ACK";
await HttpPostAsync(new { data = message });
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(500);
}
}
}
}
}
@@ -0,0 +1,163 @@
using Common.NETCore.Extensions;
using Common.NETCore.Models;
using System.Net;
using System.Text;
using HttpServer = Common.Net.Http.HttpServer;
namespace FASS.Scheduler.Services.Extends.Demo
{
public class ExtendHttpServerService
{
public ExtendService ExtendService { get; }
public bool IsRunning { get; private set; }
private readonly HttpServer _httpServer;
public ExtendHttpServerService(
ExtendService extendService)
{
ExtendService = extendService;
_httpServer = new HttpServer();
_httpServer.Prefixes = ExtendService.AppSettings.Extend.HttpServerPrefixes;
_httpServer.Started += HttpServer_Started;
_httpServer.Stopped += HttpServer_Stopped;
_httpServer.Method += HttpServer_Method;
_httpServer.Get += HttpServer_Get;
_httpServer.Post += HttpServer_Post;
}
public void Start()
{
try
{
if (IsRunning)
{
return;
}
IsRunning = true;
_httpServer.StartAndAccept();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public void Stop()
{
try
{
if (!IsRunning)
{
return;
}
IsRunning = false;
_httpServer.Stop();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
private void HttpServer_Started(HttpServer server)
{
ExtendService.Logger.LogInformation("HTTP 服务端已启动:监听[{Prefixes}]。", string.Join(',', server.Server.Prefixes));
}
private void HttpServer_Stopped(HttpServer server)
{
ExtendService.Logger.LogInformation("HTTP 服务端已停止。");
if (!IsRunning)
{
return;
}
_ = Task.Run(async () =>
{
await Task.Delay(3000);
if (IsRunning)
{
server.Start();
}
});
}
private void HttpServer_Method(HttpServer server, HttpListenerContext context, string data)
{
ExtendService.Logger.LogInformation("HTTP 请求:方法[{Method}] 地址[{Url}] 数据:{Data}", context.Request.HttpMethod, context.Request.RawUrl, data);
}
private void HttpServer_Get(HttpServer server, HttpListenerContext context, string data)
{
var responseResult = new ResponseResult();
responseResult.Success = true;
responseResult.Data = data;
SendResponse(context, responseResult);
}
private void HttpServer_Post(HttpServer server, HttpListenerContext context, string data)
{
var responseResult = new ResponseResult();
if (context.Request.RawUrl is null) return;
try
{
if (context.Request.RawUrl.Equals("/agv/carState", StringComparison.OrdinalIgnoreCase))
{
var requestJson = data.JsonParseDocument();
if (!requestJson.RootElement.TryGetProperty("carCode", out var carCodeJson))
{
responseResult.Success = false;
responseResult.Message = "获取参数失败 [carCode]";
SendResponse(context, responseResult);
return;
}
var carCode = carCodeJson.GetString();
if (string.IsNullOrWhiteSpace(carCode))
{
responseResult.Success = true;
responseResult.Data = Enumerable.Range(0, 10).Select(e => e.ToString()).ToList();
SendResponse(context, responseResult);
return;
}
var car = Enumerable.Range(0, 10).Select(e => e.ToString()).FirstOrDefault(e => e == carCode);
if (car is null)
{
responseResult.Success = false;
responseResult.Message = $"获取车辆失败 [{carCode}]";
SendResponse(context, responseResult);
return;
}
responseResult.Success = true;
responseResult.Data = car.ToString();
SendResponse(context, responseResult);
return;
}
else
{
responseResult.Success = false;
responseResult.Message = "无效接口";
SendResponse(context, responseResult);
return;
}
}
catch (Exception ex)
{
responseResult.Success = false;
responseResult.Message = ex.Message;
SendResponse(context, responseResult);
return;
}
}
public void SendResponse(HttpListenerContext context, ResponseResult responseResult)
{
ExtendService.Logger.LogInformation("HTTP 响应:方法[{Method}] 地址[{Url}] 数据:{Data}", context.Request.HttpMethod, context.Request.RawUrl, responseResult.ToJson());
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json;charset=UTF-8";
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.OutputStream.Write(Encoding.UTF8.GetBytes(responseResult.ToJson()));
context.Response.OutputStream.Close();
}
}
}
@@ -0,0 +1,123 @@
using Common.NETCore;
using Common.NETCore.Helpers;
using System.Net;
using TcpClient = Common.Net.Tcp.TcpClient;
namespace FASS.Scheduler.Services.Extends.Demo
{
public class ExtendTcpClientService
{
public ExtendService ExtendService { get; }
public bool IsRunning { get; private set; }
private readonly TcpClient _tcpClient;
public ExtendTcpClientService(
ExtendService eventService)
{
ExtendService = eventService;
var tcpClientRemoteIP = Guard.NotNull(ExtendService.AppSettings.Extend.TcpClientRemoteIP);
_tcpClient = new TcpClient() { RemoteEndPoint = IPEndPoint.Parse(tcpClientRemoteIP) };
_tcpClient.Connected += TcpClient_Connected;
_tcpClient.Disconnected += TcpClient_Disconnected;
_tcpClient.Sent += TcpClient_Sent;
_tcpClient.Received += TcpClient_Received;
}
public void Start()
{
try
{
if (IsRunning)
{
return;
}
IsRunning = true;
_tcpClient.ConnectAndReceive();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public void Stop()
{
try
{
if (!IsRunning)
{
return;
}
IsRunning = false;
_tcpClient.Disconnect();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
private void TcpClient_Connected(TcpClient client)
{
Task.Run(() => KeepaliveAsync(client));
ExtendService.Logger.LogInformation("TCP 客户端已连接:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}]。", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
}
private void TcpClient_Disconnected(TcpClient client)
{
Task.Run(() => ReconnectAsync(client));
ExtendService.Logger.LogInformation("TCP 客户端已断开。");
}
private void TcpClient_Sent(TcpClient client, byte[] data)
{
ExtendService.Logger.LogInformation("TCP 客户端已发送:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}] 数据[{Data}]。", client.Client.LocalEndPoint, client.Client.RemoteEndPoint, ByteHelper.ByteArrayToHexString(data));
}
private void TcpClient_Received(TcpClient client, byte[] data)
{
ExtendService.Logger.LogInformation("TCP 客户端已接收:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}] 数据[{Data}]。", client.Client.LocalEndPoint, client.Client.RemoteEndPoint, ByteHelper.ByteArrayToHexString(data));
}
private async Task KeepaliveAsync(TcpClient client)
{
while (IsRunning && client.IsConnected)
{
try
{
byte[] sendByteArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
client.Send(sendByteArray);
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(500);
}
}
}
private async Task ReconnectAsync(TcpClient client)
{
while (IsRunning && !client.IsConnected)
{
try
{
client.ConnectAndReceive();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(5000);
}
}
}
}
}
@@ -0,0 +1,102 @@
using Common.Net.Tcp;
using Common.NETCore;
using Common.NETCore.Helpers;
using System.Net;
using TcpServer = Common.Net.Tcp.TcpServer;
namespace FASS.Scheduler.Services.Extends.Demo
{
public class ExtendTcpServerService
{
public ExtendService ExtendService { get; }
public bool IsRunning { get; private set; }
private readonly TcpServer _tcpServer;
public ExtendTcpServerService(
ExtendService eventService)
{
ExtendService = eventService;
var tcpServerLocalIP = Guard.NotNull(ExtendService.AppSettings.Extend.TcpServerLocalIP);
_tcpServer = new TcpServer() { LocalEndPoint = IPEndPoint.Parse(tcpServerLocalIP) };
_tcpServer.Started += TcpServer_Started;
_tcpServer.Stopped += TcpServer_Stopped;
_tcpServer.Accepted += TcpServer_Accepted;
}
public void Start()
{
try
{
if (IsRunning)
{
return;
}
IsRunning = true;
_tcpServer.StartAndAccept();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public void Stop()
{
try
{
if (!IsRunning)
{
return;
}
IsRunning = false;
_tcpServer.Stop();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
private void TcpServer_Started(TcpServer server)
{
ExtendService.Logger.LogInformation("TCP 服务端已启动:本地[{LocalEndPoint}]。", server.Server.LocalEndPoint);
}
private void TcpServer_Stopped(TcpServer server)
{
ExtendService.Logger.LogInformation("TCP 服务端已停止。");
}
private void TcpServer_Accepted(TcpServer server, TcpClient client)
{
ExtendService.Logger.LogInformation("TCP 服务端已接入连接:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}]。", server.Server.LocalEndPoint, client.Client.LocalEndPoint);
client.Connected += Client_Connected;
client.Disconnected += Client_Disconnected;
client.Sent += Client_Sent;
client.Received += Client_Received;
}
private void Client_Connected(TcpClient client)
{
ExtendService.Logger.LogInformation("TCP 会话已连接:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}]。", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
}
private void Client_Disconnected(TcpClient client)
{
ExtendService.Logger.LogInformation("TCP 会话已断开。");
}
private void Client_Sent(TcpClient client, byte[] data)
{
ExtendService.Logger.LogInformation("TCP 会话已发送:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}] 数据[{Data}]。", client.Client.LocalEndPoint, client.Client.RemoteEndPoint, ByteHelper.ByteArrayToHexString(data));
}
private void Client_Received(TcpClient client, byte[] data)
{
ExtendService.Logger.LogInformation("TCP 会话已接收:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}] 数据[{Data}]。", client.Client.LocalEndPoint, client.Client.RemoteEndPoint, ByteHelper.ByteArrayToHexString(data));
client.Send(data);
}
}
}
@@ -0,0 +1,133 @@
using Common.NETCore;
using Common.NETCore.Helpers;
using System.Net;
using UdpServer = Common.Net.Udp.UdpServer;
namespace FASS.Scheduler.Services.Extends.Demo
{
public class ExtendUdpServerService
{
public ExtendService ExtendService { get; }
public bool IsRunning { get; private set; }
private readonly UdpServer _udpServer;
public ExtendUdpServerService(
ExtendService eventService)
{
ExtendService = eventService;
var udpServerLocalIP = Guard.NotNull(ExtendService.AppSettings.Extend.UdpServerLocalIP);
var udpServerRemoteIP = Guard.NotNull(ExtendService.AppSettings.Extend.UdpServerRemoteIP);
_udpServer = new UdpServer
{
LocalEndPoint = IPEndPoint.Parse(udpServerLocalIP),
RemoteEndPoint = IPEndPoint.Parse(udpServerRemoteIP)
};
_udpServer.Started += UdpServer_Started;
_udpServer.Stopped += UdpServer_Stopped;
_udpServer.Sent += UdpServer_Sent;
_udpServer.Received += UdpServer_Received;
}
public void Start()
{
try
{
if (IsRunning)
{
return;
}
IsRunning = true;
_udpServer.StartAndReceive();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
public void Stop()
{
try
{
if (!IsRunning)
{
return;
}
IsRunning = false;
_udpServer.Stop();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
}
private void UdpServer_Started(UdpServer server)
{
Task.Run(() => KeepaliveAsync(server));
ExtendService.Logger.LogInformation("UDP 服务端已启动:本地[{LocalEndPoint}]。", server.Server.LocalEndPoint);
}
private void UdpServer_Stopped(UdpServer server)
{
if (!IsRunning)
{
return;
}
Task.Run(() => ReconnectAsync(server));
ExtendService.Logger.LogInformation("UDP 服务端已停止。");
}
private void UdpServer_Sent(UdpServer server, byte[] data, EndPoint point)
{
ExtendService.Logger.LogInformation("UDP 服务端已发送:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}] 数据[{Data}]。", server.Server.LocalEndPoint, point, ByteHelper.ByteArrayToHexString(data));
}
private void UdpServer_Received(UdpServer server, byte[] data, EndPoint point)
{
ExtendService.Logger.LogInformation("UDP 服务端已接收:本地[{LocalEndPoint}] 远端[{RemoteEndPoint}] 数据[{Data}]。", server.Server.LocalEndPoint, server.Server.RemoteEndPoint, ByteHelper.ByteArrayToHexString(data));
server.Send(data, point);
}
private async Task KeepaliveAsync(UdpServer server)
{
while (IsRunning && server.IsRunning)
{
try
{
byte[] sendByteArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
server.Send(sendByteArray);
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(500);
}
}
}
private async Task ReconnectAsync(UdpServer server)
{
while (IsRunning && !server.IsRunning)
{
try
{
server.StartAndReceive();
}
catch (Exception ex)
{
ExtendService.Logger.LogError(ex, "错误");
}
finally
{
await Task.Delay(5000);
}
}
}
}
}
@@ -0,0 +1,126 @@
using FASS.Scheduler.Models;
using FASS.Scheduler.Services.Extends.Demo;
namespace FASS.Scheduler.Services.Extends
{
public class ExtendService
{
public ILogger<ExtendService> Logger { get; }
public AppSettings AppSettings { get; }
public IServiceProvider ServiceProvider { get; }
public ExtendComClientService ExtendComClientService { get; } = null!;
public ExtendHttpClientService ExtendHttpClientService { get; } = null!;
public ExtendHttpServerService ExtendHttpServerService { get; } = null!;
public ExtendTcpClientService ExtendTcpClientService { get; } = null!;
public ExtendTcpServerService ExtendTcpServerService { get; } = null!;
public ExtendUdpServerService ExtendUdpClientService { get; } = null!;
public ExtendService(
ILogger<ExtendService> logger,
AppSettings appSettings,
IServiceProvider serviceProvider)
{
Logger = logger;
AppSettings = appSettings;
ServiceProvider = serviceProvider;
if (AppSettings.Extend.EnableComClient)
{
ExtendComClientService = new ExtendComClientService(this);
}
if (AppSettings.Extend.EnableTcpClient)
{
ExtendTcpClientService = new ExtendTcpClientService(this);
}
if (AppSettings.Extend.EnableTcpServer)
{
ExtendTcpServerService = new ExtendTcpServerService(this);
}
if (AppSettings.Extend.EnableUdpServer)
{
ExtendUdpClientService = new ExtendUdpServerService(this);
}
if (AppSettings.Extend.EnableHttpClient)
{
ExtendHttpClientService = new ExtendHttpClientService(this);
}
if (AppSettings.Extend.EnableHttpServer)
{
ExtendHttpServerService = new ExtendHttpServerService(this);
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
try
{
if (AppSettings.Extend.EnableComClient)
{
ExtendComClientService.Start();
}
if (AppSettings.Extend.EnableTcpClient)
{
ExtendTcpClientService.Start();
}
if (AppSettings.Extend.EnableTcpServer)
{
ExtendTcpServerService.Start();
}
if (AppSettings.Extend.EnableUdpServer)
{
ExtendUdpClientService.Start();
}
if (AppSettings.Extend.EnableHttpClient)
{
ExtendHttpClientService.Start();
}
if (AppSettings.Extend.EnableHttpServer)
{
ExtendHttpServerService.Start();
}
}
catch (Exception ex)
{
Logger.LogError(ex, "扩展服务启动失败");
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
try
{
if (AppSettings.Extend.EnableComClient)
{
ExtendComClientService.Stop();
}
if (AppSettings.Extend.EnableTcpClient)
{
ExtendTcpClientService.Stop();
}
if (AppSettings.Extend.EnableTcpServer)
{
ExtendTcpServerService.Stop();
}
if (AppSettings.Extend.EnableUdpServer)
{
ExtendUdpClientService.Stop();
}
if (AppSettings.Extend.EnableHttpClient)
{
ExtendHttpClientService.Stop();
}
if (AppSettings.Extend.EnableHttpServer)
{
ExtendHttpServerService.Stop();
}
}
catch (Exception ex)
{
Logger.LogError(ex, "扩展服务停止失败");
}
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,21 @@
namespace FASS.Scheduler.Utility
{
public static class CacheKey
{
public static class Login
{
public static string Captcha => $"LoginCaptcha{Guid.NewGuid()}";
}
public static class Setting
{
public static string Config => "SettingConfig";
public static string DictItem => "SettingDictItem";
public static string ConfigData => "SettingConfigData";
public static string ConfigService => "SettingConfigService";
}
public static class Dashboard
{
public static string Home(string username) => $"DashboardHome{username}";
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

Some files were not shown because too many files have changed in this diff Show More