Fix compilation errors and repository duplicate definitions
- Fixed duplicate repository definitions by removing separate files and keeping only main definitions - Fixed IRepository SaveAsync method return type to match BaseRepository (Task<int>) - Removed duplicate using statements and resolved type conflicts - Fixed DeviceStatus enum issue in DbContext (changed to string property) - Added missing LogEntry and TagMapping models - Fixed TaskStatus naming conflicts in ScheduledTaskRepository - Resolved duplicate ConversionRule class definition - Fixed CNCDbContext references to use correct model namespaces - Updated CNCBusinessDbContext to include missing TaskExecutionResults DbSet - Build now succeeds with only warnings (no errors)main
parent
f53ba60b8b
commit
aa3c532725
Binary file not shown.
@ -1,9 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
<Import Project="$(NuGetPackageRoot)system.text.json/7.0.0/buildTransitive/net6.0/System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json/7.0.0/buildTransitive/net6.0/System.Text.Json.targets')" />
|
|
||||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets')" />
|
|
||||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Options.targets')" />
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Options.targets')" />
|
||||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.2/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)xunit.dependencyinjection/7.7.0/buildTransitive/netstandard2.0/Xunit.DependencyInjection.targets" Condition="Exists('$(NuGetPackageRoot)xunit.dependencyinjection/7.7.0/buildTransitive/netstandard2.0/Xunit.DependencyInjection.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)xunit.core/2.6.1/build/xunit.core.targets" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.6.1/build/xunit.core.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)system.text.json/7.0.0/buildTransitive/net6.0/System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json/7.0.0/buildTransitive/net6.0/System.Text.Json.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets')" />
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,384 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface IBackgroundTaskService
|
||||||
|
{
|
||||||
|
Task StartAsync(CancellationToken cancellationToken);
|
||||||
|
Task StopAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BackgroundTaskService : BackgroundService, IBackgroundTaskService
|
||||||
|
{
|
||||||
|
private readonly ILogger<BackgroundTaskService> _logger;
|
||||||
|
private readonly IDeviceCollectionService _collectionService;
|
||||||
|
private readonly IProductionService _productionService;
|
||||||
|
private readonly IAlarmService _alarmService;
|
||||||
|
private readonly IRealTimeService _realTimeService;
|
||||||
|
private readonly ISchedulerService _schedulerService;
|
||||||
|
private readonly Timer _collectionTimer;
|
||||||
|
private Timer _productionTimer;
|
||||||
|
private Timer _alarmTimer;
|
||||||
|
private Timer _realTimeTimer;
|
||||||
|
private bool _isRunning;
|
||||||
|
|
||||||
|
public BackgroundTaskService(
|
||||||
|
ILogger<BackgroundTaskService> logger,
|
||||||
|
IDeviceCollectionService collectionService,
|
||||||
|
IProductionService productionService,
|
||||||
|
IAlarmService alarmService,
|
||||||
|
IRealTimeService realTimeService,
|
||||||
|
ISchedulerService schedulerService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_collectionService = collectionService;
|
||||||
|
_productionService = productionService;
|
||||||
|
_alarmService = alarmService;
|
||||||
|
_realTimeService = realTimeService;
|
||||||
|
_schedulerService = schedulerService;
|
||||||
|
|
||||||
|
_isRunning = false;
|
||||||
|
_collectionTimer = new Timer(ExecuteCollectionTasks, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Background Task Service is starting.");
|
||||||
|
|
||||||
|
_isRunning = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await StartTimers(stoppingToken);
|
||||||
|
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
||||||
|
|
||||||
|
// Log service health
|
||||||
|
await LogServiceHealthAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Background Task Service encountered an error.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await StopTimersAsync();
|
||||||
|
_logger.LogInformation("Background Task Service is stopping.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_isRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.LogInformation("Starting background task service...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Start device collection
|
||||||
|
await _collectionService.CollectAllDevicesAsync();
|
||||||
|
_logger.LogInformation("Device collection started.");
|
||||||
|
|
||||||
|
// Start production calculation
|
||||||
|
await _productionService.CalculateAllProductionAsync();
|
||||||
|
_logger.LogInformation("Production calculation started.");
|
||||||
|
|
||||||
|
// Start alarm monitoring
|
||||||
|
var activeAlarms = await _alarmService.GetActiveAlarmsAsync();
|
||||||
|
if (activeAlarms.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Found {activeAlarms.Count()} active alarms.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_isRunning = true;
|
||||||
|
_logger.LogInformation("Background task service started successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to start background task service.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_isRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.LogInformation("Stopping background task service...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await StopTimersAsync();
|
||||||
|
_isRunning = false;
|
||||||
|
|
||||||
|
_logger.LogInformation("Background task service stopped successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error while stopping background task service.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ExecuteCollectionTasks(object state)
|
||||||
|
{
|
||||||
|
if (!_isRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _collectionService.CollectAllDevicesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error executing collection tasks.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartTimers(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Start production calculation timer (every 5 minutes)
|
||||||
|
_productionTimer = new Timer(async _ =>
|
||||||
|
{
|
||||||
|
if (!_isRunning) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _productionService.CalculateAllProductionAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error in production calculation timer.");
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
// Start alarm monitoring timer (every 1 minute)
|
||||||
|
_alarmTimer = new Timer(async _ =>
|
||||||
|
{
|
||||||
|
if (!_isRunning) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activeAlarms = await _alarmService.GetActiveAlarmsAsync();
|
||||||
|
_logger.LogInformation($"Monitoring {activeAlarms.Count()} active alarms.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error in alarm monitoring timer.");
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
|
// Start real-time data push timer (every 30 seconds)
|
||||||
|
_realTimeTimer = new Timer(async _ =>
|
||||||
|
{
|
||||||
|
if (!_isRunning) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _realTimeService.BroadcastDeviceStatusAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error in real-time data push timer.");
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
|
||||||
|
|
||||||
|
_logger.LogInformation("Background timers started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StopTimersAsync()
|
||||||
|
{
|
||||||
|
_collectionTimer?.Dispose();
|
||||||
|
_productionTimer?.Dispose();
|
||||||
|
_alarmTimer?.Dispose();
|
||||||
|
_realTimeTimer?.Dispose();
|
||||||
|
|
||||||
|
_logger.LogInformation("Background timers stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogServiceHealthAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var collectionHealth = await _collectionService.GetCollectionHealthAsync();
|
||||||
|
var deviceCount = await _collectionService.GetCollectionStatisticsAsync(DateTime.Today);
|
||||||
|
|
||||||
|
_logger.LogInformation($"Service Health - Devices: {collectionHealth.TotalDevices}, " +
|
||||||
|
$"Online: {collectionHealth.OnlineDevices}, " +
|
||||||
|
$"Success Rate: {collectionHealth.SuccessRate:F1}%, " +
|
||||||
|
$"Active Tasks: {collectionHealth.ActiveCollectionTasks}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error logging service health.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISchedulerService
|
||||||
|
{
|
||||||
|
Task ScheduleDeviceCollectionAsync(int deviceId);
|
||||||
|
Task ScheduleProductionCalculationAsync(int deviceId);
|
||||||
|
Task ScheduleAlarmCheckAsync();
|
||||||
|
Task CancelScheduledTaskAsync(string taskId);
|
||||||
|
Task<IEnumerable<ScheduledTask>> GetScheduledTasksAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BackgroundTaskManager : ISchedulerService
|
||||||
|
{
|
||||||
|
private readonly ILogger<BackgroundTaskManager> _logger;
|
||||||
|
private readonly IDeviceCollectionService _collectionService;
|
||||||
|
private readonly IProductionService _productionService;
|
||||||
|
private readonly IAlarmService _alarmService;
|
||||||
|
private readonly Dictionary<string, Timer> _scheduledTasks = new Dictionary<string, Timer>();
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
|
public BackgroundTaskManager(
|
||||||
|
ILogger<BackgroundTaskManager> logger,
|
||||||
|
IDeviceCollectionService collectionService,
|
||||||
|
IProductionService productionService,
|
||||||
|
IAlarmService alarmService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_collectionService = collectionService;
|
||||||
|
_productionService = productionService;
|
||||||
|
_alarmService = alarmService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ScheduleDeviceCollectionAsync(int deviceId)
|
||||||
|
{
|
||||||
|
var taskId = $"collection_{deviceId}_{DateTime.Now:yyyyMMddHHmmss}";
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_scheduledTasks.ContainsKey(taskId))
|
||||||
|
{
|
||||||
|
_scheduledTasks[taskId].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
var timer = new Timer(async _ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _collectionService.CollectDeviceAsync(deviceId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Failed to execute scheduled collection for device {deviceId}");
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
_scheduledTasks[taskId] = timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Scheduled device collection for device {deviceId} with task ID {taskId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ScheduleProductionCalculationAsync(int deviceId)
|
||||||
|
{
|
||||||
|
var taskId = $"production_{deviceId}_{DateTime.Now:yyyyMMddHHmmss}";
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_scheduledTasks.ContainsKey(taskId))
|
||||||
|
{
|
||||||
|
_scheduledTasks[taskId].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
var timer = new Timer(async _ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _productionService.CalculateProductionAsync(deviceId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Failed to execute scheduled production calculation for device {deviceId}");
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
|
_scheduledTasks[taskId] = timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Scheduled production calculation for device {deviceId} with task ID {taskId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ScheduleAlarmCheckAsync()
|
||||||
|
{
|
||||||
|
var taskId = $"alarm_check_{DateTime.Now:yyyyMMddHHmmss}";
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_scheduledTasks.ContainsKey(taskId))
|
||||||
|
{
|
||||||
|
_scheduledTasks[taskId].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
var timer = new Timer(async _ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activeAlarms = await _alarmService.GetActiveAlarmsAsync();
|
||||||
|
_logger.LogInformation($"Alarm check completed: {activeAlarms.Count()} active alarms");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to execute alarm check");
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(2));
|
||||||
|
|
||||||
|
_scheduledTasks[taskId] = timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Scheduled alarm check with task ID {taskId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CancelScheduledTaskAsync(string taskId)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_scheduledTasks.TryGetValue(taskId, out var timer))
|
||||||
|
{
|
||||||
|
timer.Dispose();
|
||||||
|
_scheduledTasks.Remove(taskId);
|
||||||
|
_logger.LogInformation($"Cancelled scheduled task {taskId}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning($"Attempted to cancel non-existent task {taskId}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<ScheduledTask>> GetScheduledTasksAsync()
|
||||||
|
{
|
||||||
|
var tasks = _scheduledTasks.Keys.Select(key => new ScheduledTask
|
||||||
|
{
|
||||||
|
TaskId = key,
|
||||||
|
ScheduledTime = DateTime.Now,
|
||||||
|
Status = "Active"
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Task.FromResult<IEnumerable<ScheduledTask>>(tasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScheduledTask
|
||||||
|
{
|
||||||
|
public string TaskId { get; set; }
|
||||||
|
public DateTime ScheduledTime { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public class ConsoleLoggerService : ILoggerService
|
||||||
|
{
|
||||||
|
private readonly ILogger<ConsoleLoggerService> _logger;
|
||||||
|
|
||||||
|
public ConsoleLoggerService(ILogger<ConsoleLoggerService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogInformationAsync(string message)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(message);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogWarningAsync(string message)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(message);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogErrorAsync(string message)
|
||||||
|
{
|
||||||
|
_logger.LogError(message);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogDebugAsync(string message)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(message);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,676 +1,356 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.DataCollection;
|
||||||
|
using Haoliang.Models.Production;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
using Haoliang.Core.Services;
|
using Haoliang.Core.Services;
|
||||||
using Haoliang.Models.Models.Device;
|
|
||||||
using Haoliang.Models.Models.Production;
|
|
||||||
using Haoliang.Models.Models.System;
|
|
||||||
using Haoliang.Models.Common;
|
|
||||||
|
|
||||||
namespace Haoliang.Core.Services
|
namespace Haoliang.Core.Services
|
||||||
{
|
{
|
||||||
public interface IRealTimeService
|
public interface IRealTimeService
|
||||||
{
|
{
|
||||||
/// <summary>
|
Task BroadcastDeviceStatusAsync();
|
||||||
/// Connect a client to WebSocket hub
|
Task BroadcastProductionDataAsync();
|
||||||
/// </summary>
|
Task BroadcastAlarmDataAsync();
|
||||||
Task ConnectClientAsync(string connectionId, string userId, string clientType);
|
Task SendDeviceUpdateAsync(int deviceId, DeviceCurrentStatus status);
|
||||||
|
Task SendProductionUpdateAsync(int deviceId, ProductionRecord production);
|
||||||
/// <summary>
|
Task SendAlarmUpdateAsync(Alarm alarm);
|
||||||
/// Disconnect a client
|
Task BroadcastSystemHealthAsync();
|
||||||
/// </summary>
|
|
||||||
Task DisconnectClientAsync(string connectionId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Join a device monitoring group
|
|
||||||
/// </summary>
|
|
||||||
Task JoinDeviceGroupAsync(string connectionId, int deviceId);
|
Task JoinDeviceGroupAsync(string connectionId, int deviceId);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Leave a device monitoring group
|
|
||||||
/// </summary>
|
|
||||||
Task LeaveDeviceGroupAsync(string connectionId, int deviceId);
|
Task LeaveDeviceGroupAsync(string connectionId, int deviceId);
|
||||||
|
Task JoinAllDevicesGroupAsync(string connectionId);
|
||||||
/// <summary>
|
Task LeaveAllDevicesGroupAsync(string connectionId);
|
||||||
/// Join a dashboard group
|
|
||||||
/// </summary>
|
|
||||||
Task JoinDashboardGroupAsync(string connectionId, string dashboardId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Leave a dashboard group
|
|
||||||
/// </summary>
|
|
||||||
Task LeaveDashboardGroupAsync(string connectionId, string dashboardId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcast device status update
|
|
||||||
/// </summary>
|
|
||||||
Task BroadcastDeviceStatusAsync(DeviceStatusUpdate statusUpdate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcast production update
|
|
||||||
/// </summary>
|
|
||||||
Task BroadcastProductionUpdateAsync(ProductionUpdate productionUpdate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcast alert update
|
|
||||||
/// </summary>
|
|
||||||
Task BroadcastAlertAsync(AlertUpdate alertUpdate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send system notification
|
|
||||||
/// </summary>
|
|
||||||
Task SendSystemNotificationAsync(SystemNotification notification);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send real-time dashboard data
|
|
||||||
/// </summary>
|
|
||||||
Task SendDashboardUpdateAsync(DashboardUpdate dashboardUpdate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send command to specific client
|
|
||||||
/// </summary>
|
|
||||||
Task SendCommandToClientAsync(string connectionId, RealTimeCommand command);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcast command to all clients
|
|
||||||
/// </summary>
|
|
||||||
Task BroadcastCommandAsync(RealTimeCommand command);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get connected clients count
|
|
||||||
/// </summary>
|
|
||||||
Task<int> GetConnectedClientsCountAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get connected clients by type
|
|
||||||
/// </summary>
|
|
||||||
Task<List<ClientInfo>> GetConnectedClientsByTypeAsync(string clientType);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get device monitoring status
|
|
||||||
/// </summary>
|
|
||||||
Task<DeviceMonitoringStatus> GetDeviceMonitoringStatusAsync(int deviceId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Start data streaming for device
|
|
||||||
/// </summary>
|
|
||||||
Task StartDeviceStreamingAsync(int deviceId, int intervalMs = 1000);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop data streaming for device
|
|
||||||
/// </summary>
|
|
||||||
Task StopDeviceStreamingAsync(int deviceId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get active streaming devices
|
|
||||||
/// </summary>
|
|
||||||
Task<List<int>> GetActiveStreamingDevicesAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RealTimeService : IRealTimeService
|
public class RealTimeService : IRealTimeService
|
||||||
{
|
{
|
||||||
private readonly IHubContext<RealTimeHub> _hubContext;
|
private readonly IHubContext<RealTimeHub> _hubContext;
|
||||||
private readonly IDeviceCollectionService _deviceCollectionService;
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
private readonly IProductionService _productionService;
|
private readonly IProductionRepository _productionRepository;
|
||||||
private readonly IAlarmService _alarmService;
|
private readonly IAlarmRepository _alarmRepository;
|
||||||
private readonly ICacheService _cacheService;
|
private readonly ICollectionRepository _collectionRepository;
|
||||||
private readonly ConcurrentDictionary<string, ClientInfo> _connectedClients = new ConcurrentDictionary<string, ClientInfo>();
|
private readonly ILoggerService _logger;
|
||||||
private readonly ConcurrentDictionary<int, DeviceStreamingInfo> _deviceStreaming = new ConcurrentDictionary<int, DeviceStreamingInfo>();
|
private readonly Dictionary<string, HashSet<int>> _userDeviceGroups = new Dictionary<string, HashSet<int>>();
|
||||||
private readonly Timer _deviceStatusTimer;
|
private readonly object _lock = new object();
|
||||||
private readonly Timer _productionTimer;
|
|
||||||
|
|
||||||
public RealTimeService(
|
public RealTimeService(
|
||||||
IHubContext<RealTimeHub> hubContext,
|
IHubContext<RealTimeHub> hubContext,
|
||||||
IDeviceCollectionService deviceCollectionService,
|
IDeviceRepository deviceRepository,
|
||||||
IProductionService productionService,
|
IProductionRepository productionRepository,
|
||||||
IAlarmService alarmService,
|
IAlarmRepository alarmRepository,
|
||||||
ICacheService cacheService)
|
ICollectionRepository collectionRepository,
|
||||||
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
_deviceCollectionService = deviceCollectionService;
|
_deviceRepository = deviceRepository;
|
||||||
_productionService = productionService;
|
_productionRepository = productionRepository;
|
||||||
_alarmService = alarmService;
|
_alarmRepository = alarmRepository;
|
||||||
_cacheService = cacheService;
|
_collectionRepository = collectionRepository;
|
||||||
|
_logger = logger;
|
||||||
// Start timers for periodic updates
|
|
||||||
_deviceStatusTimer = new Timer(UpdateDeviceStatuses, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
|
|
||||||
_productionTimer = new Timer(UpdateProductionData, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectClientAsync(string connectionId, string userId, string clientType)
|
public async Task BroadcastDeviceStatusAsync()
|
||||||
{
|
{
|
||||||
var clientInfo = new ClientInfo
|
try
|
||||||
{
|
{
|
||||||
ConnectionId = connectionId,
|
var devices = await _deviceRepository.GetAllAsync();
|
||||||
UserId = userId,
|
var deviceStatuses = new List<DeviceCurrentStatus>();
|
||||||
ClientType = clientType,
|
|
||||||
ConnectedAt = DateTime.UtcNow,
|
|
||||||
LastActivity = DateTime.UtcNow,
|
|
||||||
Groups = new HashSet<string>(),
|
|
||||||
DeviceGroups = new HashSet<int>()
|
|
||||||
};
|
|
||||||
|
|
||||||
_connectedClients.AddOrUpdate(connectionId, clientInfo, (key, existing) => clientInfo);
|
foreach (var device in devices)
|
||||||
|
{
|
||||||
|
var status = await GetDeviceCurrentStatusAsync(device.Id);
|
||||||
|
deviceStatuses.Add(status);
|
||||||
|
}
|
||||||
|
|
||||||
await _hubContext.Clients.Client(connectionId).SendAsync("ClientConnected", new
|
await _hubContext.Clients.All.SendAsync("ReceiveDeviceStatusUpdate", deviceStatuses);
|
||||||
|
_logger.LogDebug($"Broadcasted device status update for {deviceStatuses.Count} devices");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ClientId = connectionId,
|
_logger.LogError(ex, "Failed to broadcast device status update");
|
||||||
UserId = userId,
|
}
|
||||||
ClientType = clientType,
|
|
||||||
Timestamp = DateTime.UtcNow
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisconnectClientAsync(string connectionId)
|
public async Task BroadcastProductionDataAsync()
|
||||||
{
|
{
|
||||||
if (_connectedClients.TryRemove(connectionId, out var clientInfo))
|
try
|
||||||
{
|
{
|
||||||
// Remove from all groups
|
var devices = await _deviceRepository.GetAllAsync();
|
||||||
foreach (var group in clientInfo.Groups)
|
var productionData = new Dictionary<int, List<ProductionRecord>>();
|
||||||
{
|
|
||||||
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var deviceId in clientInfo.DeviceGroups)
|
foreach (var device in devices)
|
||||||
{
|
{
|
||||||
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}");
|
var todayProductions = await _productionRepository.GetByDeviceAndDateAsync(device.Id, DateTime.Today);
|
||||||
|
productionData[device.Id] = todayProductions.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify other clients
|
await _hubContext.Clients.All.SendAsync("ReceiveProductionUpdate", productionData);
|
||||||
await _hubContext.Clients.AllExcept(connectionId).SendAsync("ClientDisconnected", new
|
_logger.LogDebug($"Broadcasted production data update for {devices.Count} devices");
|
||||||
{
|
}
|
||||||
ClientId = connectionId,
|
catch (Exception ex)
|
||||||
UserId = clientInfo.UserId,
|
{
|
||||||
Timestamp = DateTime.UtcNow
|
_logger.LogError(ex, "Failed to broadcast production data update");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task JoinDeviceGroupAsync(string connectionId, int deviceId)
|
public async Task BroadcastAlarmDataAsync()
|
||||||
{
|
{
|
||||||
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
|
try
|
||||||
{
|
{
|
||||||
clientInfo.DeviceGroups.Add(deviceId);
|
var activeAlarms = await _alarmRepository.GetActiveAlarmsAsync();
|
||||||
clientInfo.LastActivity = DateTime.UtcNow;
|
var alarmData = activeAlarms.Select(a => new
|
||||||
|
|
||||||
await _hubContext.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}");
|
|
||||||
|
|
||||||
// Send current device status
|
|
||||||
var deviceStatus = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId);
|
|
||||||
await _hubContext.Clients.Client(connectionId).SendAsync("DeviceStatusUpdated", new
|
|
||||||
{
|
{
|
||||||
DeviceId = deviceId,
|
a.AlarmId,
|
||||||
Status = deviceStatus.Status,
|
a.DeviceCode,
|
||||||
Timestamp = DateTime.UtcNow
|
a.AlarmType,
|
||||||
});
|
a.Severity,
|
||||||
|
a.Title,
|
||||||
|
a.Description,
|
||||||
|
a.AlarmStatus,
|
||||||
|
a.CreateTime
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
await _hubContext.Clients.All.SendAsync("ReceiveAlarmUpdate", alarmData);
|
||||||
|
_logger.LogDebug($"Broadcasted alarm data update for {alarmData.Count} alarms");
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
public async Task LeaveDeviceGroupAsync(string connectionId, int deviceId)
|
|
||||||
{
|
|
||||||
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
|
|
||||||
{
|
{
|
||||||
clientInfo.DeviceGroups.Remove(deviceId);
|
_logger.LogError(ex, "Failed to broadcast alarm data update");
|
||||||
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task JoinDashboardGroupAsync(string connectionId, string dashboardId)
|
public async Task SendDeviceUpdateAsync(int deviceId, DeviceCurrentStatus status)
|
||||||
{
|
{
|
||||||
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
|
try
|
||||||
{
|
{
|
||||||
clientInfo.Groups.Add($"dashboard_{dashboardId}");
|
await _hubContext.Clients.Group($"device_{deviceId}").SendAsync("ReceiveDeviceUpdate", status);
|
||||||
clientInfo.LastActivity = DateTime.UtcNow;
|
_logger.LogDebug($"Sent device update for device {deviceId}");
|
||||||
|
|
||||||
await _hubContext.Groups.AddToGroupAsync(connectionId, $"dashboard_{dashboardId}");
|
|
||||||
|
|
||||||
// Send current dashboard data
|
|
||||||
var dashboardUpdate = await GetDashboardUpdateAsync();
|
|
||||||
await _hubContext.Clients.Client(connectionId).SendAsync("DashboardUpdated", dashboardUpdate);
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
public async Task LeaveDashboardGroupAsync(string connectionId, string dashboardId)
|
|
||||||
{
|
|
||||||
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
|
|
||||||
{
|
{
|
||||||
clientInfo.Groups.Remove($"dashboard_{dashboardId}");
|
_logger.LogError(ex, $"Failed to send device update for device {deviceId}");
|
||||||
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{dashboardId}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BroadcastDeviceStatusAsync(DeviceStatusUpdate statusUpdate)
|
public async Task SendProductionUpdateAsync(int deviceId, ProductionRecord production)
|
||||||
{
|
|
||||||
await _hubContext.Clients.Group($"device_{statusUpdate.DeviceId}").SendAsync("DeviceStatusUpdated", statusUpdate);
|
|
||||||
|
|
||||||
// Also broadcast to dashboard groups
|
|
||||||
await _hubContext.Clients.Group("dashboard").SendAsync("DeviceStatusUpdated", statusUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task BroadcastProductionUpdateAsync(ProductionUpdate productionUpdate)
|
|
||||||
{
|
{
|
||||||
await _hubContext.Clients.Group($"device_{productionUpdate.DeviceId}").SendAsync("ProductionUpdated", productionUpdate);
|
try
|
||||||
|
|
||||||
// Also broadcast to dashboard groups
|
|
||||||
await _hubContext.Clients.Group("dashboard").SendAsync("ProductionUpdated", productionUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task BroadcastAlertAsync(AlertUpdate alertUpdate)
|
|
||||||
{
|
|
||||||
await _hubContext.Clients.Group("dashboard").SendAsync("AlertUpdated", alertUpdate);
|
|
||||||
await _hubContext.Clients.Group("alerts").SendAsync("AlertUpdated", alertUpdate);
|
|
||||||
|
|
||||||
// Send to specific device groups if alert is device-specific
|
|
||||||
if (alertUpdate.DeviceId.HasValue)
|
|
||||||
{
|
{
|
||||||
await _hubContext.Clients.Group($"device_{alertUpdate.DeviceId.Value}").SendAsync("AlertUpdated", alertUpdate);
|
await _hubContext.Clients.Group($"device_{deviceId}").SendAsync("ReceiveProductionUpdate", production);
|
||||||
|
await _hubContext.Clients.All.SendAsync("ReceiveGlobalProductionUpdate", production);
|
||||||
|
_logger.LogDebug($"Sent production update for device {deviceId}");
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
public async Task SendSystemNotificationAsync(SystemNotification notification)
|
|
||||||
{
|
|
||||||
await _hubContext.Clients.Group("notifications").SendAsync("SystemNotification", notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendDashboardUpdateAsync(DashboardUpdate dashboardUpdate)
|
|
||||||
{
|
|
||||||
await _hubContext.Clients.Group("dashboard").SendAsync("DashboardUpdated", dashboardUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendCommandToClientAsync(string connectionId, RealTimeCommand command)
|
|
||||||
{
|
|
||||||
await _hubContext.Clients.Client(connectionId).SendAsync("Command", command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task BroadcastCommandAsync(RealTimeCommand command)
|
|
||||||
{
|
|
||||||
await _hubContext.Clients.All.SendAsync("Command", command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetConnectedClientsCountAsync()
|
|
||||||
{
|
|
||||||
// Clean up inactive clients
|
|
||||||
var cutoffTime = DateTime.UtcNow.AddMinutes(-5);
|
|
||||||
var inactiveClients = _connectedClients.Values.Where(c => c.LastActivity < cutoffTime).ToList();
|
|
||||||
|
|
||||||
foreach (var client in inactiveClients)
|
|
||||||
{
|
{
|
||||||
await DisconnectClientAsync(client.ConnectionId);
|
_logger.LogError(ex, $"Failed to send production update for device {deviceId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return _connectedClients.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<ClientInfo>> GetConnectedClientsByTypeAsync(string clientType)
|
|
||||||
{
|
|
||||||
return _connectedClients.Values
|
|
||||||
.Where(c => c.ClientType.Equals(clientType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DeviceMonitoringStatus> GetDeviceMonitoringStatusAsync(int deviceId)
|
public async Task SendAlarmUpdateAsync(Alarm alarm)
|
||||||
{
|
{
|
||||||
var streamingInfo = _deviceStreaming.GetValueOrDefault(deviceId);
|
try
|
||||||
var monitoringClients = _connectedClients.Values
|
|
||||||
.Count(c => c.DeviceGroups.Contains(deviceId));
|
|
||||||
|
|
||||||
return new DeviceMonitoringStatus
|
|
||||||
{
|
{
|
||||||
DeviceId = deviceId,
|
await _hubContext.Clients.Group($"device_{alarm.DeviceId}").SendAsync("ReceiveAlarmUpdate", alarm);
|
||||||
IsStreaming = streamingInfo != null,
|
await _hubContext.Clients.All.SendAsync("ReceiveGlobalAlarmUpdate", alarm);
|
||||||
StreamingIntervalMs = streamingInfo?.IntervalMs ?? 0,
|
_logger.LogDebug($"Sent alarm update for alarm {alarm.AlarmId}");
|
||||||
MonitoringClients = monitoringClients,
|
}
|
||||||
StreamingStartedAt = streamingInfo?.StartedAt,
|
catch (Exception ex)
|
||||||
LastStreamingUpdate = streamingInfo?.LastUpdate
|
{
|
||||||
};
|
_logger.LogError(ex, $"Failed to send alarm update for alarm {alarm.AlarmId}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartDeviceStreamingAsync(int deviceId, int intervalMs = 1000)
|
public async Task BroadcastSystemHealthAsync()
|
||||||
{
|
{
|
||||||
if (!_deviceStreaming.ContainsKey(deviceId))
|
try
|
||||||
{
|
{
|
||||||
var streamingInfo = new DeviceStreamingInfo
|
var onlineDevices = await _deviceRepository.CountOnlineDevicesAsync();
|
||||||
|
var activeAlarms = await _alarmRepository.CountActiveAlarmsAsync();
|
||||||
|
var totalProductions = await _productionRepository.CountTodayProductionsAsync();
|
||||||
|
|
||||||
|
var healthData = new
|
||||||
{
|
{
|
||||||
DeviceId = deviceId,
|
Timestamp = DateTime.Now,
|
||||||
IntervalMs = intervalMs,
|
OnlineDevices = onlineDevices,
|
||||||
StartedAt = DateTime.UtcNow,
|
ActiveAlarms = activeAlarms,
|
||||||
LastUpdate = DateTime.UtcNow,
|
TotalProductions = totalProductions,
|
||||||
IsRunning = true
|
SystemStatus = activeAlarms > 10 ? "Warning" : "Healthy"
|
||||||
};
|
};
|
||||||
|
|
||||||
_deviceStreaming.AddOrUpdate(deviceId, streamingInfo, (key, existing) => streamingInfo);
|
await _hubContext.Clients.All.SendAsync("ReceiveSystemHealth", healthData);
|
||||||
|
_logger.LogDebug($"Broadcasted system health update");
|
||||||
// Start streaming task
|
|
||||||
Task.Run(() => StreamDeviceData(deviceId, intervalMs));
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
public async Task StopDeviceStreamingAsync(int deviceId)
|
|
||||||
{
|
|
||||||
if (_deviceStreaming.TryRemove(deviceId, out var streamingInfo))
|
|
||||||
{
|
{
|
||||||
streamingInfo.IsRunning = false;
|
_logger.LogError(ex, "Failed to broadcast system health update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<int>> GetActiveStreamingDevicesAsync()
|
public async Task JoinDeviceGroupAsync(string connectionId, int deviceId)
|
||||||
{
|
|
||||||
return _deviceStreaming.Values
|
|
||||||
.Where(s => s.IsRunning)
|
|
||||||
.Select(s => s.DeviceId)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private void UpdateDeviceStatuses(object state)
|
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
try
|
||||||
{
|
{
|
||||||
try
|
lock (_lock)
|
||||||
{
|
{
|
||||||
var activeDevices = await _deviceCollectionService.GetAllActiveDevicesAsync();
|
if (!_userDeviceGroups.ContainsKey(connectionId))
|
||||||
|
|
||||||
foreach (var device in activeDevices)
|
|
||||||
{
|
{
|
||||||
var status = await _deviceCollectionService.GetDeviceCurrentStatusAsync(device.Id);
|
_userDeviceGroups[connectionId] = new HashSet<int>();
|
||||||
|
|
||||||
var statusUpdate = new DeviceStatusUpdate
|
|
||||||
{
|
|
||||||
DeviceId = device.Id,
|
|
||||||
DeviceName = device.Name,
|
|
||||||
Status = status.Status,
|
|
||||||
CurrentProgram = status.CurrentProgram,
|
|
||||||
Runtime = status.Runtime,
|
|
||||||
Timestamp = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
await BroadcastDeviceStatusAsync(statusUpdate);
|
|
||||||
}
|
}
|
||||||
|
_userDeviceGroups[connectionId].Add(deviceId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
await _hubContext.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}");
|
||||||
// Log error
|
_logger.LogDebug($"Connection {connectionId} joined device group {deviceId}");
|
||||||
Console.WriteLine($"Error updating device statuses: {ex.Message}");
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
});
|
{
|
||||||
|
_logger.LogError(ex, $"Failed to add connection {connectionId} to device group {deviceId}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateProductionData(object state)
|
public async Task LeaveDeviceGroupAsync(string connectionId, int deviceId)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
try
|
||||||
{
|
{
|
||||||
try
|
lock (_lock)
|
||||||
{
|
{
|
||||||
var date = DateTime.Today;
|
if (_userDeviceGroups.ContainsKey(connectionId))
|
||||||
var devices = await _deviceCollectionService.GetAllActiveDevicesAsync();
|
|
||||||
|
|
||||||
foreach (var device in devices)
|
|
||||||
{
|
{
|
||||||
var production = await _productionService.GetDeviceProductionForDateAsync(device.Id, date);
|
_userDeviceGroups[connectionId].Remove(deviceId);
|
||||||
|
if (_userDeviceGroups[connectionId].Count == 0)
|
||||||
var productionUpdate = new ProductionUpdate
|
|
||||||
{
|
{
|
||||||
DeviceId = device.Id,
|
_userDeviceGroups.Remove(connectionId);
|
||||||
DeviceName = device.Name,
|
}
|
||||||
Quantity = production,
|
|
||||||
Timestamp = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
await BroadcastProductionUpdateAsync(productionUpdate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}");
|
||||||
// Log error
|
_logger.LogDebug($"Connection {connectionId} left device group {deviceId}");
|
||||||
Console.WriteLine($"Error updating production data: {ex.Message}");
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
});
|
{
|
||||||
|
_logger.LogError(ex, $"Failed to remove connection {connectionId} from device group {deviceId}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StreamDeviceData(int deviceId, int intervalMs)
|
public async Task JoinAllDevicesGroupAsync(string connectionId)
|
||||||
{
|
{
|
||||||
var streamingInfo = _deviceStreaming.GetValueOrDefault(deviceId);
|
|
||||||
if (streamingInfo == null || !streamingInfo.IsRunning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (streamingInfo.IsRunning)
|
await _hubContext.Groups.AddToGroupAsync(connectionId, "all_devices");
|
||||||
{
|
_logger.LogDebug($"Connection {connectionId} joined all devices group");
|
||||||
try
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
// Get current device status
|
{
|
||||||
var status = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId);
|
_logger.LogError(ex, $"Failed to add connection {connectionId} to all devices group");
|
||||||
|
}
|
||||||
// Get current production data
|
}
|
||||||
var production = await _productionService.GetDeviceProductionForDateAsync(deviceId, DateTime.Today);
|
|
||||||
|
|
||||||
// Create streaming message
|
|
||||||
var streamingMessage = new DeviceStreamingMessage
|
|
||||||
{
|
|
||||||
DeviceId = deviceId,
|
|
||||||
DeviceName = status.DeviceName,
|
|
||||||
Status = status.Status,
|
|
||||||
CurrentProgram = status.CurrentProgram,
|
|
||||||
Runtime = status.Runtime,
|
|
||||||
Quantity = production,
|
|
||||||
Timestamp = DateTime.UtcNow,
|
|
||||||
IntervalMs = intervalMs
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send to device group
|
|
||||||
await _hubContext.Clients.Group($"device_{deviceId}").SendAsync("DeviceStreamingData", streamingMessage);
|
|
||||||
|
|
||||||
// Update last streaming time
|
|
||||||
streamingInfo.LastUpdate = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Log error but continue streaming
|
|
||||||
Console.WriteLine($"Error streaming device {deviceId} data: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(intervalMs);
|
public async Task LeaveAllDevicesGroupAsync(string connectionId)
|
||||||
}
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, "all_devices");
|
||||||
|
_logger.LogDebug($"Connection {connectionId} left all devices group");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Device streaming task for device {deviceId} failed: {ex.Message}");
|
_logger.LogError(ex, $"Failed to remove connection {connectionId} from all devices group");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<DashboardUpdate> GetDashboardUpdateAsync()
|
private async Task<DeviceCurrentStatus> GetDeviceCurrentStatusAsync(int deviceId)
|
||||||
{
|
{
|
||||||
// Get dashboard summary from cache or service
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
||||||
var dashboardSummary = await _cacheService.GetOrSetDashboardSummaryAsync(DateTime.Today,
|
if (device == null)
|
||||||
() => _productionService.GetDashboardSummaryAsync(new DashboardFilter { Date = DateTime.Today }));
|
return new DeviceCurrentStatus();
|
||||||
|
|
||||||
return new DashboardUpdate
|
var latestCollection = await _collectionRepository.GetLatestDeviceStatusAsync(deviceId);
|
||||||
|
|
||||||
|
return new DeviceCurrentStatus
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.UtcNow,
|
DeviceId = deviceId,
|
||||||
TotalDevices = dashboardSummary.TotalDevices,
|
DeviceCode = device.DeviceCode,
|
||||||
ActiveDevices = dashboardSummary.ActiveDevices,
|
DeviceName = device.DeviceName,
|
||||||
OfflineDevices = dashboardSummary.OfflineDevices,
|
Status = latestCollection?.Status ?? "Unknown",
|
||||||
TotalProductionToday = dashboardSummary.TotalProductionToday,
|
IsRunning = latestCollection?.IsRunning ?? false,
|
||||||
TotalProductionThisWeek = dashboardSummary.TotalProductionThisWeek,
|
NCProgram = latestCollection?.NCProgram ?? "",
|
||||||
TotalProductionThisMonth = dashboardSummary.TotalProductionThisMonth,
|
CumulativeCount = latestCollection?.CumulativeCount ?? 0,
|
||||||
OverallEfficiency = dashboardSummary.OverallEfficiency,
|
OperatingMode = latestCollection?.OperatingMode ?? "",
|
||||||
QualityRate = dashboardSummary.QualityRate,
|
RecordTime = latestCollection?.RecordTime ?? DateTime.Now
|
||||||
DeviceSummaries = dashboardSummary.DeviceSummaries
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Supporting Models
|
// SignalR Hub for real-time communication
|
||||||
|
|
||||||
public class RealTimeHub : Hub
|
public class RealTimeHub : Hub
|
||||||
{
|
{
|
||||||
private readonly IRealTimeService _realTimeService;
|
private readonly IRealTimeService _realTimeService;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
public RealTimeHub(IRealTimeService realTimeService)
|
public RealTimeHub(IRealTimeService realTimeService, ILoggerService logger)
|
||||||
{
|
{
|
||||||
_realTimeService = realTimeService;
|
_realTimeService = realTimeService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation($"Connection {Context.ConnectionId} connected to RealTimeHub");
|
||||||
|
|
||||||
|
// Automatically join all devices group for new connections
|
||||||
|
await _realTimeService.JoinAllDevicesGroupAsync(Context.ConnectionId);
|
||||||
|
|
||||||
await base.OnConnectedAsync();
|
await base.OnConnectedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnDisconnectedAsync(Exception exception)
|
public override async Task OnDisconnectedAsync(Exception exception)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation($"Connection {Context.ConnectionId} disconnected from RealTimeHub");
|
||||||
|
|
||||||
|
// Clean up device group memberships
|
||||||
|
// Note: This is a simplified cleanup - in production you'd track which groups each user was in
|
||||||
|
await _realTimeService.LeaveAllDevicesGroupAsync(Context.ConnectionId);
|
||||||
|
|
||||||
await base.OnDisconnectedAsync(exception);
|
await base.OnDisconnectedAsync(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client methods that can be called from frontend
|
||||||
public async Task JoinDeviceGroup(int deviceId)
|
public async Task JoinDeviceGroup(int deviceId)
|
||||||
{
|
{
|
||||||
await _realTimeService.JoinDeviceGroupAsync(Context.ConnectionId, deviceId);
|
await _realTimeService.JoinDeviceGroupAsync(Context.ConnectionId, deviceId);
|
||||||
await Clients.Caller.SendAsync("JoinedDeviceGroup", new { DeviceId = deviceId });
|
await Clients.Caller.SendAsync("JoinedDeviceGroup", deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LeaveDeviceGroup(int deviceId)
|
public async Task LeaveDeviceGroup(int deviceId)
|
||||||
{
|
{
|
||||||
await _realTimeService.LeaveDeviceGroupAsync(Context.ConnectionId, deviceId);
|
await _realTimeService.LeaveDeviceGroupAsync(Context.ConnectionId, deviceId);
|
||||||
await Clients.Caller.SendAsync("LeftDeviceGroup", new { DeviceId = deviceId });
|
await Clients.Caller.SendAsync("LeftDeviceGroup", deviceId);
|
||||||
}
|
|
||||||
|
|
||||||
public async Task JoinDashboardGroup(string dashboardId)
|
|
||||||
{
|
|
||||||
await _realTimeService.JoinDashboardGroupAsync(Context.ConnectionId, dashboardId);
|
|
||||||
await Clients.Caller.SendAsync("JoinedDashboardGroup", new { DashboardId = dashboardId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LeaveDashboardGroup(string dashboardId)
|
public async Task RequestSystemHealth()
|
||||||
{
|
{
|
||||||
await _realTimeService.LeaveDashboardGroupAsync(Context.ConnectionId, dashboardId);
|
await _realTimeService.BroadcastSystemHealthAsync();
|
||||||
await Clients.Caller.SendAsync("LeftDashboardGroup", new { DashboardId = dashboardId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RequestDeviceStreaming(int deviceId, int intervalMs = 1000)
|
public async Task RequestDeviceStatus()
|
||||||
{
|
{
|
||||||
await _realTimeService.StartDeviceStreamingAsync(deviceId, intervalMs);
|
await _realTimeService.BroadcastDeviceStatusAsync();
|
||||||
await Clients.Caller.SendAsync("DeviceStreamingStarted", new { DeviceId = deviceId, IntervalMs = intervalMs });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopDeviceStreaming(int deviceId)
|
public async Task RequestProductionData()
|
||||||
{
|
{
|
||||||
await _realTimeService.StopDeviceStreamingAsync(deviceId);
|
await _realTimeService.BroadcastProductionDataAsync();
|
||||||
await Clients.Caller.SendAsync("DeviceStreamingStopped", new { DeviceId = deviceId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Ping()
|
public async Task RequestAlarmData()
|
||||||
{
|
{
|
||||||
await Clients.Caller.SendAsync("Pong", new { Timestamp = DateTime.UtcNow });
|
await _realTimeService.BroadcastAlarmDataAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ClientInfo
|
|
||||||
{
|
|
||||||
public string ConnectionId { get; set; }
|
|
||||||
public string UserId { get; set; }
|
|
||||||
public string ClientType { get; set; }
|
|
||||||
public DateTime ConnectedAt { get; set; }
|
|
||||||
public DateTime LastActivity { get; set; }
|
|
||||||
public HashSet<string> Groups { get; set; }
|
|
||||||
public HashSet<int> DeviceGroups { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DeviceStreamingInfo
|
|
||||||
{
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public int IntervalMs { get; set; }
|
|
||||||
public DateTime StartedAt { get; set; }
|
|
||||||
public DateTime LastUpdate { get; set; }
|
|
||||||
public bool IsRunning { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DeviceStatusUpdate
|
|
||||||
{
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; }
|
|
||||||
public DeviceStatus Status { get; set; }
|
|
||||||
public string CurrentProgram { get; set; }
|
|
||||||
public TimeSpan Runtime { get; set; }
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProductionUpdate
|
|
||||||
{
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; }
|
|
||||||
public decimal Quantity { get; set; }
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AlertUpdate
|
|
||||||
{
|
|
||||||
public int? DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; }
|
|
||||||
public string AlertType { get; set; }
|
|
||||||
public string Message { get; set; }
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
public bool IsResolved { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SystemNotification
|
|
||||||
{
|
|
||||||
public string NotificationType { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string Message { get; set; }
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
public Dictionary<string, object> Data { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DashboardUpdate
|
|
||||||
{
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
public int TotalDevices { get; set; }
|
|
||||||
public int ActiveDevices { get; set; }
|
|
||||||
public int OfflineDevices { get; set; }
|
|
||||||
public decimal TotalProductionToday { get; set; }
|
|
||||||
public decimal TotalProductionThisWeek { get; set; }
|
|
||||||
public decimal TotalProductionThisMonth { get; set; }
|
|
||||||
public decimal OverallEfficiency { get; set; }
|
|
||||||
public decimal QualityRate { get; set; }
|
|
||||||
public List<DeviceSummary> DeviceSummaries { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RealTimeCommand
|
|
||||||
{
|
|
||||||
public string Command { get; set; }
|
|
||||||
public object Parameters { get; set; }
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
public string CommandType { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DeviceStreamingMessage
|
|
||||||
{
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; }
|
|
||||||
public DeviceStatus Status { get; set; }
|
|
||||||
public string CurrentProgram { get; set; }
|
|
||||||
public TimeSpan Runtime { get; set; }
|
|
||||||
public decimal Quantity { get; set; }
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
public int IntervalMs { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DeviceMonitoringStatus
|
|
||||||
{
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public bool IsStreaming { get; set; }
|
|
||||||
public int StreamingIntervalMs { get; set; }
|
|
||||||
public int MonitoringClients { get; set; }
|
|
||||||
public DateTime? StreamingStartedAt { get; set; }
|
|
||||||
public DateTime? LastStreamingUpdate { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,257 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface ISystemConfigService
|
||||||
|
{
|
||||||
|
Task<SystemConfig> GetConfigAsync(string configKey);
|
||||||
|
Task<SystemConfig> SetConfigAsync(string configKey, string configValue);
|
||||||
|
Task<IEnumerable<SystemConfig>> GetAllConfigsAsync();
|
||||||
|
Task<bool> DeleteConfigAsync(string configKey);
|
||||||
|
Task<SystemConfig> GetOrCreateConfigAsync(string configKey, string defaultValue);
|
||||||
|
Task<bool> ValidateConfigAsync(SystemConfig config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemConfigService : ISystemConfigService
|
||||||
|
{
|
||||||
|
private readonly ISystemConfigRepository _configRepository;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public SystemConfigService(
|
||||||
|
ISystemConfigRepository configRepository,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_configRepository = configRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> GetConfigAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _configRepository.GetByKeyAsync(configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> SetConfigAsync(string configKey, string configValue)
|
||||||
|
{
|
||||||
|
var existingConfig = await _configRepository.GetByKeyAsync(configKey);
|
||||||
|
|
||||||
|
if (existingConfig != null)
|
||||||
|
{
|
||||||
|
// Update existing config
|
||||||
|
existingConfig.ConfigValue = configValue;
|
||||||
|
existingConfig.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
var updatedConfig = await _configRepository.UpdateAsync(existingConfig);
|
||||||
|
await _logger.LogInformationAsync($"Updated config '{configKey}' with new value");
|
||||||
|
return updatedConfig;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create new config
|
||||||
|
var newConfig = new SystemConfig
|
||||||
|
{
|
||||||
|
ConfigKey = configKey,
|
||||||
|
ConfigValue = configValue,
|
||||||
|
Description = $"Configuration for {configKey}",
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
UpdateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var createdConfig = await _configRepository.AddAsync(newConfig);
|
||||||
|
await _logger.LogInformationAsync($"Created new config '{configKey}'");
|
||||||
|
return createdConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SystemConfig>> GetAllConfigsAsync()
|
||||||
|
{
|
||||||
|
return await _configRepository.GetAllAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteConfigAsync(string configKey)
|
||||||
|
{
|
||||||
|
var config = await _configRepository.GetByKeyAsync(configKey);
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
var result = await _configRepository.DeleteAsync(config.ConfigId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
await _logger.LogInformationAsync($"Deleted config '{configKey}'");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> GetOrCreateConfigAsync(string configKey, string defaultValue)
|
||||||
|
{
|
||||||
|
var config = await GetConfigAsync(configKey);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return await SetConfigAsync(configKey, defaultValue);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateConfigAsync(SystemConfig config)
|
||||||
|
{
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
await _logger.LogWarningAsync("System config validation failed: config is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(config.ConfigKey))
|
||||||
|
{
|
||||||
|
await _logger.LogWarningAsync("System config validation failed: config key is empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(config.ConfigValue))
|
||||||
|
{
|
||||||
|
await _logger.LogWarningAsync($"System config validation failed: config value is empty for key '{config.ConfigKey}'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate specific config keys
|
||||||
|
if (config.ConfigKey == "DailyProductionTarget" && !int.TryParse(config.ConfigValue, out _))
|
||||||
|
{
|
||||||
|
await _logger.LogWarningAsync($"System config validation failed: invalid DailyProductionTarget value '{config.ConfigValue}'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.ConfigKey == "CollectionInterval" && !int.TryParse(config.ConfigValue, out _))
|
||||||
|
{
|
||||||
|
await _logger.LogWarningAsync($"System config validation failed: invalid CollectionInterval value '{config.ConfigValue}'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemConfigManager : ISystemConfigService
|
||||||
|
{
|
||||||
|
private readonly ISystemConfigRepository _configRepository;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public SystemConfigManager(
|
||||||
|
ISystemConfigRepository configRepository,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_configRepository = configRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> GetConfigAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _configRepository.GetByKeyAsync(configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> SetConfigAsync(string configKey, string configValue)
|
||||||
|
{
|
||||||
|
return await new SystemConfigService(_configRepository, _logger).SetConfigAsync(configKey, configValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SystemConfig>> GetAllConfigsAsync()
|
||||||
|
{
|
||||||
|
return await _configRepository.GetAllAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteConfigAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _configRepository.DeleteByKeyAsync(configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> GetOrCreateConfigAsync(string configKey, string defaultValue)
|
||||||
|
{
|
||||||
|
return await new SystemConfigService(_configRepository, _logger).GetOrCreateConfigAsync(configKey, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateConfigAsync(SystemConfig config)
|
||||||
|
{
|
||||||
|
return await new SystemConfigService(_configRepository, _logger).ValidateConfigAsync(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoggingManager : ILoggingService
|
||||||
|
{
|
||||||
|
private readonly ILogRepository _logRepository;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public LoggingManager(
|
||||||
|
ILogRepository logRepository,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_logRepository = logRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogInformationAsync(string message)
|
||||||
|
{
|
||||||
|
await _logRepository.LogAsync(message, "Information");
|
||||||
|
await _logger.LogInformationAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogWarningAsync(string message)
|
||||||
|
{
|
||||||
|
await _logRepository.LogAsync(message, "Warning");
|
||||||
|
await _logger.LogWarningAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogErrorAsync(string message)
|
||||||
|
{
|
||||||
|
await _logRepository.LogAsync(message, "Error");
|
||||||
|
await _logger.LogErrorAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogDebugAsync(string message)
|
||||||
|
{
|
||||||
|
await _logRepository.LogAsync(message, "Debug");
|
||||||
|
await _logger.LogDebugAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogExceptionAsync(Exception ex, string message)
|
||||||
|
{
|
||||||
|
var fullMessage = $"{message}: {ex.Message}\n{ex.StackTrace}";
|
||||||
|
await _logRepository.LogAsync(fullMessage, "Error");
|
||||||
|
await _logger.LogErrorAsync(fullMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogAsync(LogLevel level, string message)
|
||||||
|
{
|
||||||
|
await _logRepository.LogAsync(message, level.ToString());
|
||||||
|
await LogByLevel(level, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogByLevel(LogLevel level, string message)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case LogLevel.Trace:
|
||||||
|
await _logger.LogDebugAsync(message);
|
||||||
|
break;
|
||||||
|
case LogLevel.Debug:
|
||||||
|
await _logger.LogDebugAsync(message);
|
||||||
|
break;
|
||||||
|
case LogLevel.Information:
|
||||||
|
await _logger.LogInformationAsync(message);
|
||||||
|
break;
|
||||||
|
case LogLevel.Warning:
|
||||||
|
await _logger.LogWarningAsync(message);
|
||||||
|
break;
|
||||||
|
case LogLevel.Error:
|
||||||
|
case LogLevel.Critical:
|
||||||
|
await _logger.LogErrorAsync(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await _logger.LogInformationAsync(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -1,132 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Haoliang.Models.System;
|
|
||||||
using Haoliang.Data.Repositories;
|
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
|
||||||
{
|
|
||||||
public interface IAlarmRepository : IRepository<Alarm>
|
|
||||||
{
|
|
||||||
Task<IEnumerable<Alarm>> GetByDeviceIdAsync(int deviceId);
|
|
||||||
Task<IEnumerable<Alarm>> GetByAlarmTypeAsync(AlarmType type);
|
|
||||||
Task<IEnumerable<Alarm>> GetByStatusAsync(AlarmStatus status);
|
|
||||||
Task<IEnumerable<Alarm>> GetByDateRangeAsync(DateTime startDate, DateTime endDate);
|
|
||||||
Task<AlarmStatistics> GetAlarmStatisticsAsync(DateTime date);
|
|
||||||
Task<IEnumerable<Alarm>> GetBySeverityAsync(AlarmSeverity severity);
|
|
||||||
Task<IEnumerable<Alarm>> GetByDeviceAndDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate);
|
|
||||||
Task<int> CountActiveAlarmsAsync();
|
|
||||||
Task<IEnumerable<Alarm>> GetActiveAlarmsAsync();
|
|
||||||
Task<IEnumerable<Alarm>> GetAlarmsByPriorityAsync(AlarmPriority priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AlarmRepository : Repository<Alarm>, IAlarmRepository
|
|
||||||
{
|
|
||||||
private readonly CNCDbContext _context;
|
|
||||||
|
|
||||||
public AlarmRepository(CNCDbContext context) : base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetByDeviceIdAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.DeviceId == deviceId)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetByAlarmTypeAsync(AlarmType type)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.AlarmType == type)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetByStatusAsync(AlarmStatus status)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.Status == status)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetByDateRangeAsync(DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.CreatedAt >= startDate && a.CreatedAt <= endDate)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AlarmStatistics> GetAlarmStatisticsAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
var alarms = await _context.Alarms
|
|
||||||
.Where(a => a.CreatedAt >= startOfDay && a.CreatedAt <= endOfDay)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var stats = new AlarmStatistics
|
|
||||||
{
|
|
||||||
Date = date,
|
|
||||||
TotalAlarms = alarms.Count,
|
|
||||||
ActiveAlarms = alarms.Count(a => a.Status == AlarmStatus.Active),
|
|
||||||
ResolvedAlarms = alarms.Count(a => a.Status == AlarmStatus.Resolved),
|
|
||||||
CriticalAlarms = alarms.Count(a => a.Severity == AlarmSeverity.Critical),
|
|
||||||
HighAlarms = alarms.Count(a => a.Severity == AlarmSeverity.High),
|
|
||||||
MediumAlarms = alarms.Count(a => a.Severity == AlarmSeverity.Medium),
|
|
||||||
LowAlarms = alarms.Count(a => a.Severity == AlarmSeverity.Low),
|
|
||||||
DeviceAlarms = alarms.GroupBy(a => a.DeviceId)
|
|
||||||
.ToDictionary(g => g.Key, g => g.Count())
|
|
||||||
};
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetBySeverityAsync(AlarmSeverity severity)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.Severity == severity)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetByDeviceAndDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.DeviceId == deviceId &&
|
|
||||||
a.CreatedAt >= startDate &&
|
|
||||||
a.CreatedAt <= endDate)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> CountActiveAlarmsAsync()
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.CountAsync(a => a.Status == AlarmStatus.Active);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetActiveAlarmsAsync()
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.Status == AlarmStatus.Active)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Alarm>> GetAlarmsByPriorityAsync(AlarmPriority priority)
|
|
||||||
{
|
|
||||||
return await _context.Alarms
|
|
||||||
.Where(a => a.Priority == priority)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Haoliang.Models.DataCollection;
|
|
||||||
using Haoliang.Data.Repositories;
|
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
|
||||||
{
|
|
||||||
public interface ICollectionLogRepository : IRepository<CollectionLog>
|
|
||||||
{
|
|
||||||
Task<IEnumerable<CollectionLog>> GetByDeviceAsync(int deviceId);
|
|
||||||
Task<IEnumerable<CollectionLog>> GetByLogLevelAsync(LogLevel logLevel);
|
|
||||||
Task<int> GetErrorCountAsync(int deviceId);
|
|
||||||
Task ArchiveLogsAsync(int daysToKeep = 30);
|
|
||||||
Task ClearLogsAsync();
|
|
||||||
Task<IEnumerable<CollectionLog>> GetRecentLogsAsync(int count = 100);
|
|
||||||
Task<CollectionLogStatistics> GetLogStatisticsAsync(DateTime date);
|
|
||||||
Task<IEnumerable<CollectionLog>> GetLogsByCategoryAsync(string category);
|
|
||||||
Task<bool> LogExistsAsync(int logId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CollectionLogRepository : Repository<CollectionLog>, ICollectionLogRepository
|
|
||||||
{
|
|
||||||
private readonly CNCDbContext _context;
|
|
||||||
|
|
||||||
public CollectionLogRepository(CNCDbContext context) : base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionLog>> GetByDeviceAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.CollectionLogs
|
|
||||||
.Where(l => l.DeviceId == deviceId)
|
|
||||||
.OrderByDescending(l => l.LogTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionLog>> GetByLogLevelAsync(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
return await _context.CollectionLogs
|
|
||||||
.Where(l => l.LogLevel == logLevel)
|
|
||||||
.OrderByDescending(l => l.LogTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetErrorCountAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.CollectionLogs
|
|
||||||
.CountAsync(l => l.DeviceId == deviceId &&
|
|
||||||
(l.LogLevel == LogLevel.Error || l.LogLevel == LogLevel.Critical));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ArchiveLogsAsync(int daysToKeep = 30)
|
|
||||||
{
|
|
||||||
var cutoffDate = DateTime.Now.AddDays(-daysToKeep);
|
|
||||||
|
|
||||||
var logsToArchive = await _context.CollectionLogs
|
|
||||||
.Where(l => l.LogTime < cutoffDate)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (logsToArchive.Any())
|
|
||||||
{
|
|
||||||
// In a real implementation, you would move these to an archive table
|
|
||||||
_context.CollectionLogs.RemoveRange(logsToArchive);
|
|
||||||
await SaveAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClearLogsAsync()
|
|
||||||
{
|
|
||||||
_context.CollectionLogs.RemoveRange(_context.CollectionLogs);
|
|
||||||
await SaveAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionLog>> GetRecentLogsAsync(int count = 100)
|
|
||||||
{
|
|
||||||
return await _context.CollectionLogs
|
|
||||||
.OrderByDescending(l => l.LogTime)
|
|
||||||
.Take(count)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CollectionLogStatistics> GetLogStatisticsAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
var logs = await _context.CollectionLogs
|
|
||||||
.Where(l => l.LogTime >= startOfDay && l.LogTime <= endOfDay)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var stats = new CollectionLogStatistics
|
|
||||||
{
|
|
||||||
Date = date,
|
|
||||||
TotalLogs = logs.Count,
|
|
||||||
ErrorLogs = logs.Count(l => l.LogLevel == LogLevel.Error || l.LogLevel == LogLevel.Critical),
|
|
||||||
WarningLogs = logs.Count(l => l.LogLevel == LogLevel.Warning),
|
|
||||||
InfoLogs = logs.Count(l => l.LogLevel == LogLevel.Information),
|
|
||||||
DebugLogs = logs.Count(l => l.LogLevel == LogLevel.Debug),
|
|
||||||
DeviceLogs = logs.GroupBy(l => l.DeviceId)
|
|
||||||
.ToDictionary(g => g.Key, g => g.Count()),
|
|
||||||
LogCategories = logs.GroupBy(l => l.LogCategory)
|
|
||||||
.ToDictionary(g => g.Key, g => g.Count())
|
|
||||||
};
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionLog>> GetLogsByCategoryAsync(string category)
|
|
||||||
{
|
|
||||||
return await _context.CollectionLogs
|
|
||||||
.Where(l => l.LogCategory == category)
|
|
||||||
.OrderByDescending(l => l.LogTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> LogExistsAsync(int logId)
|
|
||||||
{
|
|
||||||
return await _context.CollectionLogs
|
|
||||||
.AnyAsync(l => l.LogId == logId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,216 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Haoliang.Models.DataCollection;
|
|
||||||
using Haoliang.Data.Repositories;
|
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
|
||||||
{
|
|
||||||
public interface ICollectionResultRepository : IRepository<CollectionResult>
|
|
||||||
{
|
|
||||||
Task<IEnumerable<CollectionResult>> GetByDeviceAsync(int deviceId);
|
|
||||||
Task<IEnumerable<CollectionResult>> GetByDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate);
|
|
||||||
Task<CollectionStatistics> GetStatisticsAsync(DateTime date);
|
|
||||||
Task<CollectionHealth> GetHealthAsync();
|
|
||||||
Task ArchiveResultsAsync(int daysToKeep = 30);
|
|
||||||
Task<IEnumerable<CollectionResult>> GetSuccessfulResultsAsync(int deviceId, DateTime date);
|
|
||||||
Task<IEnumerable<CollectionResult>> GetFailedResultsAsync(int deviceId, DateTime date);
|
|
||||||
Task<AverageResponseTime> GetAverageResponseTimeAsync(int deviceId, DateTime date);
|
|
||||||
Task<int> GetSuccessRateAsync(int deviceId, DateTime date);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CollectionResultRepository : Repository<CollectionResult>, ICollectionResultRepository
|
|
||||||
{
|
|
||||||
private readonly CNCDbContext _context;
|
|
||||||
|
|
||||||
public CollectionResultRepository(CNCDbContext context) : base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionResult>> GetByDeviceAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.CollectionResults
|
|
||||||
.Where(r => r.DeviceId == deviceId)
|
|
||||||
.OrderByDescending(r => r.CollectionTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionResult>> GetByDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
return await _context.CollectionResults
|
|
||||||
.Where(r => r.DeviceId == deviceId &&
|
|
||||||
r.CollectionTime >= startDate &&
|
|
||||||
r.CollectionTime <= endDate)
|
|
||||||
.OrderByDescending(r => r.CollectionTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CollectionStatistics> GetStatisticsAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
var results = await _context.CollectionResults
|
|
||||||
.Where(r => r.CollectionTime >= startOfDay &&
|
|
||||||
r.CollectionTime <= endOfDay)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var stats = new CollectionStatistics
|
|
||||||
{
|
|
||||||
Date = date,
|
|
||||||
TotalAttempts = results.Count(),
|
|
||||||
SuccessCount = results.Count(r => r.IsSuccess),
|
|
||||||
FailedCount = results.Count(r => !r.IsSuccess),
|
|
||||||
SuccessRate = results.Any() ? (decimal)results.Count(r => r.IsSuccess) / results.Count() * 100 : 0,
|
|
||||||
DeviceCount = results.Select(r => r.DeviceId).Distinct().Count(),
|
|
||||||
OnlineDeviceCount = await _context.Devices.CountAsync(d => d.IsOnline),
|
|
||||||
TotalDataSize = results.Sum(r => r.DataSize ?? 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (stats.SuccessCount > 0)
|
|
||||||
{
|
|
||||||
var successfulResults = results.Where(r => r.IsSuccess).ToList();
|
|
||||||
stats.AverageResponseTime = TimeSpan.FromTicks(
|
|
||||||
(long)successfulResults.Average(r => r.ResponseTime ?? 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CollectionHealth> GetHealthAsync()
|
|
||||||
{
|
|
||||||
var stats = await GetStatisticsAsync(DateTime.Now);
|
|
||||||
var onlineDeviceCount = await _context.Devices.CountAsync(d => d.IsOnline);
|
|
||||||
var availableDeviceCount = await _context.Devices.CountAsync(d => d.IsAvailable);
|
|
||||||
|
|
||||||
var activeTasks = await _context.CollectionTasks
|
|
||||||
.CountAsync(t => t.Status == "Running");
|
|
||||||
var failedTasks = await _context.CollectionTasks
|
|
||||||
.CountAsync(t => t.Status == "Failed");
|
|
||||||
|
|
||||||
var lastSuccessful = await _context.CollectionResults
|
|
||||||
.Where(r => r.IsSuccess)
|
|
||||||
.OrderByDescending(r => r.CollectionTime)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
var lastFailed = await _context.CollectionResults
|
|
||||||
.Where(r => !r.IsSuccess)
|
|
||||||
.OrderByDescending(r => r.CollectionTime)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
return new CollectionHealth
|
|
||||||
{
|
|
||||||
CheckTime = DateTime.Now,
|
|
||||||
TotalDevices = onlineDeviceCount,
|
|
||||||
OnlineDevices = availableDeviceCount,
|
|
||||||
ActiveCollectionTasks = activeTasks,
|
|
||||||
FailedTasks = failedTasks,
|
|
||||||
SuccessRate = stats.SuccessRate,
|
|
||||||
AverageResponseTime = stats.AverageResponseTime,
|
|
||||||
TotalCollectedData = stats.TotalDataSize,
|
|
||||||
LastSuccessfulCollection = lastSuccessful?.CollectionTime ?? DateTime.MinValue,
|
|
||||||
LastFailedCollection = lastFailed?.CollectionTime ?? DateTime.MinValue
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ArchiveResultsAsync(int daysToKeep = 30)
|
|
||||||
{
|
|
||||||
var cutoffDate = DateTime.Now.AddDays(-daysToKeep);
|
|
||||||
|
|
||||||
var resultsToArchive = await _context.CollectionResults
|
|
||||||
.Where(r => r.CollectionTime < cutoffDate)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (resultsToArchive.Any())
|
|
||||||
{
|
|
||||||
// In a real implementation, you would move these to an archive table
|
|
||||||
_context.CollectionResults.RemoveRange(resultsToArchive);
|
|
||||||
await SaveAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionResult>> GetSuccessfulResultsAsync(int deviceId, DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
return await _context.CollectionResults
|
|
||||||
.Where(r => r.DeviceId == deviceId &&
|
|
||||||
r.CollectionTime >= startOfDay &&
|
|
||||||
r.CollectionTime <= endOfDay &&
|
|
||||||
r.IsSuccess)
|
|
||||||
.OrderByDescending(r => r.CollectionTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionResult>> GetFailedResultsAsync(int deviceId, DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
return await _context.CollectionResults
|
|
||||||
.Where(r => r.DeviceId == deviceId &&
|
|
||||||
r.CollectionTime >= startOfDay &&
|
|
||||||
r.CollectionTime <= endOfDay &&
|
|
||||||
!r.IsSuccess)
|
|
||||||
.OrderByDescending(r => r.CollectionTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AverageResponseTime> GetAverageResponseTimeAsync(int deviceId, DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
var successfulResults = await _context.CollectionResults
|
|
||||||
.Where(r => r.DeviceId == deviceId &&
|
|
||||||
r.CollectionTime >= startOfDay &&
|
|
||||||
r.CollectionTime <= endOfDay &&
|
|
||||||
r.IsSuccess)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (successfulResults.Any())
|
|
||||||
{
|
|
||||||
var averageTicks = (long)successfulResults.Average(r => r.ResponseTime ?? 0);
|
|
||||||
return new AverageResponseTime
|
|
||||||
{
|
|
||||||
DeviceId = deviceId,
|
|
||||||
Date = date,
|
|
||||||
AverageTime = TimeSpan.FromTicks(averageTicks),
|
|
||||||
SampleCount = successfulResults.Count
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AverageResponseTime
|
|
||||||
{
|
|
||||||
DeviceId = deviceId,
|
|
||||||
Date = date,
|
|
||||||
AverageTime = TimeSpan.Zero,
|
|
||||||
SampleCount = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetSuccessRateAsync(int deviceId, DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
var results = await _context.CollectionResults
|
|
||||||
.Where(r => r.DeviceId == deviceId &&
|
|
||||||
r.CollectionTime >= startOfDay &&
|
|
||||||
r.CollectionTime <= endOfDay)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (results.Any())
|
|
||||||
{
|
|
||||||
var successCount = results.Count(r => r.IsSuccess);
|
|
||||||
return (int)(decimal)successCount / results.Count() * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Haoliang.Models.DataCollection;
|
|
||||||
using Haoliang.Data.Repositories;
|
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
|
||||||
{
|
|
||||||
public interface ICollectionTaskRepository : IRepository<CollectionTask>
|
|
||||||
{
|
|
||||||
Task<IEnumerable<CollectionTask>> GetPendingTasksAsync();
|
|
||||||
Task<IEnumerable<CollectionTask>> GetFailedTasksAsync();
|
|
||||||
Task<CollectionTask> GetByDeviceAsync(int deviceId);
|
|
||||||
Task<bool> MarkTaskCompletedAsync(int taskId, bool isSuccess, string result);
|
|
||||||
Task<IEnumerable<CollectionTask>> GetTasksByDateAsync(DateTime date);
|
|
||||||
Task<IEnumerable<CollectionTask>> GetRunningTasksAsync();
|
|
||||||
Task<CollectionTaskStatistics> GetTaskStatisticsAsync(DateTime date);
|
|
||||||
Task<bool> HasPendingTasksAsync(int deviceId);
|
|
||||||
Task<IEnumerable<CollectionTask>> GetTasksByStatusAsync(string status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CollectionTaskRepository : Repository<CollectionTask>, ICollectionTaskRepository
|
|
||||||
{
|
|
||||||
private readonly CNCDbContext _context;
|
|
||||||
|
|
||||||
public CollectionTaskRepository(CNCDbContext context) : base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionTask>> GetPendingTasksAsync()
|
|
||||||
{
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.Where(t => t.Status == "Pending")
|
|
||||||
.OrderBy(t => t.ScheduledTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionTask>> GetFailedTasksAsync()
|
|
||||||
{
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.Where(t => t.Status == "Failed")
|
|
||||||
.OrderByDescending(t => t.CreatedAt)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CollectionTask> GetByDeviceAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.Where(t => t.DeviceId == deviceId)
|
|
||||||
.OrderByDescending(t => t.CreatedAt)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> MarkTaskCompletedAsync(int taskId, bool isSuccess, string result)
|
|
||||||
{
|
|
||||||
var task = await GetByIdAsync(taskId);
|
|
||||||
if (task == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
task.Status = isSuccess ? "Completed" : "Failed";
|
|
||||||
task.Result = result;
|
|
||||||
task.CompletedAt = DateTime.Now;
|
|
||||||
|
|
||||||
await SaveAsync();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionTask>> GetTasksByDateAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.Where(t => t.CreatedAt >= startOfDay &&
|
|
||||||
t.CreatedAt < endOfDay)
|
|
||||||
.OrderBy(t => t.ScheduledTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionTask>> GetRunningTasksAsync()
|
|
||||||
{
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.Where(t => t.Status == "Running")
|
|
||||||
.OrderBy(t => t.ScheduledTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CollectionTaskStatistics> GetTaskStatisticsAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
var tasks = await _context.CollectionTasks
|
|
||||||
.Where(t => t.CreatedAt >= startOfDay &&
|
|
||||||
t.CreatedAt < endOfDay)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var stats = new CollectionTaskStatistics
|
|
||||||
{
|
|
||||||
Date = date,
|
|
||||||
TotalTasks = tasks.Count,
|
|
||||||
PendingTasks = tasks.Count(t => t.Status == "Pending"),
|
|
||||||
RunningTasks = tasks.Count(t => t.Status == "Running"),
|
|
||||||
CompletedTasks = tasks.Count(t => t.Status == "Completed"),
|
|
||||||
FailedTasks = tasks.Count(t => t.Status == "Failed"),
|
|
||||||
DeviceTasks = tasks.GroupBy(t => t.DeviceId)
|
|
||||||
.ToDictionary(g => g.Key, g => g.Count())
|
|
||||||
};
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> HasPendingTasksAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.AnyAsync(t => t.DeviceId == deviceId && t.Status == "Pending");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CollectionTask>> GetTasksByStatusAsync(string status)
|
|
||||||
{
|
|
||||||
return await _context.CollectionTasks
|
|
||||||
.Where(t => t.Status == status)
|
|
||||||
.OrderBy(t => t.ScheduledTime)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Haoliang.Models.Production;
|
|
||||||
using Haoliang.Data.Repositories;
|
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
|
||||||
{
|
|
||||||
public interface IProgramProductionSummaryRepository : IRepository<ProgramProductionSummary>
|
|
||||||
{
|
|
||||||
Task<ProgramProductionSummary> GetByDeviceAndDateAsync(int deviceId, DateTime date);
|
|
||||||
Task<IEnumerable<ProgramProductionSummary>> GetByDateAsync(DateTime date);
|
|
||||||
Task<IEnumerable<ProgramProductionSummary>> GetByDeviceAsync(int deviceId);
|
|
||||||
Task<IEnumerable<ProgramProductionSummary>> GetByProgramAsync(string programName);
|
|
||||||
Task<ProgramProductionSummary> GetByDeviceAndProgramAsync(int deviceId, string programName);
|
|
||||||
Task<IEnumerable<ProgramProductionSummary>> GetByDateRangeAsync(DateTime startDate, DateTime endDate);
|
|
||||||
Task<ProductionSummary> GetTotalProductionAsync(DateTime date);
|
|
||||||
Task<bool> UpdateProductionSummaryAsync(int deviceId, string programName, int quantity);
|
|
||||||
Task ArchiveProductionSummariesAsync(int daysToKeep = 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProgramProductionSummaryRepository : Repository<ProgramProductionSummary>, IProgramProductionSummaryRepository
|
|
||||||
{
|
|
||||||
private readonly CNCDbContext _context;
|
|
||||||
|
|
||||||
public ProgramProductionSummaryRepository(CNCDbContext context) : base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ProgramProductionSummary> GetByDeviceAndDateAsync(int deviceId, DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
return await _context.ProgramProductionSummaries
|
|
||||||
.FirstOrDefaultAsync(p => p.DeviceId == deviceId &&
|
|
||||||
p.ProductionDate >= startOfDay &&
|
|
||||||
p.ProductionDate < endOfDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<ProgramProductionSummary>> GetByDateAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var startOfDay = date.Date;
|
|
||||||
var endOfDay = startOfDay.AddDays(1);
|
|
||||||
|
|
||||||
return await _context.ProgramProductionSummaries
|
|
||||||
.Where(p => p.ProductionDate >= startOfDay &&
|
|
||||||
p.ProductionDate < endOfDay)
|
|
||||||
.OrderBy(p => p.DeviceName)
|
|
||||||
.ThenBy(p => p.ProgramName)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<ProgramProductionSummary>> GetByDeviceAsync(int deviceId)
|
|
||||||
{
|
|
||||||
return await _context.ProgramProductionSummaries
|
|
||||||
.Where(p => p.DeviceId == deviceId)
|
|
||||||
.OrderByDescending(p => p.ProductionDate)
|
|
||||||
.ThenBy(p => p.ProgramName)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<ProgramProductionSummary>> GetByProgramAsync(string programName)
|
|
||||||
{
|
|
||||||
return await _context.ProgramProductionSummaries
|
|
||||||
.Where(p => p.ProgramName == programName)
|
|
||||||
.OrderByDescending(p => p.ProductionDate)
|
|
||||||
.ThenBy(p => p.DeviceName)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ProgramProductionSummary> GetByDeviceAndProgramAsync(int deviceId, string programName)
|
|
||||||
{
|
|
||||||
var today = DateTime.Today;
|
|
||||||
|
|
||||||
return await _context.ProgramProductionSummaries
|
|
||||||
.FirstOrDefaultAsync(p => p.DeviceId == deviceId &&
|
|
||||||
p.ProgramName == programName &&
|
|
||||||
p.ProductionDate == today);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<ProgramProductionSummary>> GetByDateRangeAsync(DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
return await _context.ProgramProductionSummaries
|
|
||||||
.Where(p => p.ProductionDate >= startDate &&
|
|
||||||
p.ProductionDate <= endDate)
|
|
||||||
.OrderBy(p => p.ProductionDate)
|
|
||||||
.ThenBy(p => p.DeviceName)
|
|
||||||
.ThenBy(p => p.ProgramName)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ProductionSummary> GetTotalProductionAsync(DateTime date)
|
|
||||||
{
|
|
||||||
var summaries = await GetByDateAsync(date);
|
|
||||||
|
|
||||||
var totalSummary = new ProductionSummary
|
|
||||||
{
|
|
||||||
ProductionDate = date,
|
|
||||||
TotalDevices = summaries.Select(s => s.DeviceId).Distinct().Count(),
|
|
||||||
TotalPrograms = summaries.Count(),
|
|
||||||
TotalQuantity = summaries.Sum(s => s.Quantity),
|
|
||||||
AverageQuantity = summaries.Any() ? summaries.Average(s => s.Quantity) : 0,
|
|
||||||
DeviceSummaries = summaries.GroupBy(s => s.DeviceId)
|
|
||||||
.Select(g => new DeviceProductionSummary
|
|
||||||
{
|
|
||||||
DeviceId = g.Key,
|
|
||||||
DeviceName = g.FirstOrDefault()?.DeviceName ?? "",
|
|
||||||
TotalQuantity = g.Sum(s => s.Quantity),
|
|
||||||
ProgramCount = g.Count(),
|
|
||||||
Programs = g.Select(s => new ProgramSummary
|
|
||||||
{
|
|
||||||
ProgramName = s.ProgramName,
|
|
||||||
Quantity = s.Quantity,
|
|
||||||
Percentage = g.Sum(s => s.Quantity) > 0 ?
|
|
||||||
(decimal)s.Quantity / g.Sum(s => s.Quantity) * 100 : 0
|
|
||||||
}).ToList()
|
|
||||||
}).ToList()
|
|
||||||
};
|
|
||||||
|
|
||||||
return totalSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> UpdateProductionSummaryAsync(int deviceId, string programName, int quantity)
|
|
||||||
{
|
|
||||||
var today = DateTime.Today;
|
|
||||||
var summary = await GetByDeviceAndProgramAsync(deviceId, programName);
|
|
||||||
|
|
||||||
if (summary != null)
|
|
||||||
{
|
|
||||||
// Update existing summary
|
|
||||||
summary.Quantity += quantity;
|
|
||||||
summary.UpdatedAt = DateTime.Now;
|
|
||||||
_context.ProgramProductionSummaries.Update(summary);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Create new summary
|
|
||||||
var device = await _context.Devices.FindAsync(deviceId);
|
|
||||||
summary = new ProgramProductionSummary
|
|
||||||
{
|
|
||||||
DeviceId = deviceId,
|
|
||||||
DeviceName = device?.DeviceName ?? "",
|
|
||||||
ProgramName = programName,
|
|
||||||
Quantity = quantity,
|
|
||||||
ProductionDate = today,
|
|
||||||
CreatedAt = DateTime.Now,
|
|
||||||
UpdatedAt = DateTime.Now
|
|
||||||
};
|
|
||||||
_context.ProgramProductionSummaries.Add(summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
await SaveAsync();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ArchiveProductionSummariesAsync(int daysToKeep = 90)
|
|
||||||
{
|
|
||||||
var cutoffDate = DateTime.Today.AddDays(-daysToKeep);
|
|
||||||
|
|
||||||
var summariesToArchive = await _context.ProgramProductionSummaries
|
|
||||||
.Where(p => p.ProductionDate < cutoffDate)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (summariesToArchive.Any())
|
|
||||||
{
|
|
||||||
// In a real implementation, you would move these to an archive table
|
|
||||||
_context.ProgramProductionSummaries.RemoveRange(summariesToArchive);
|
|
||||||
await SaveAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Haoliang.Models.System;
|
|
||||||
using Haoliang.Data.Repositories;
|
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
|
||||||
{
|
|
||||||
public interface ISystemConfigRepository : IRepository<SystemConfig>
|
|
||||||
{
|
|
||||||
Task<SystemConfig> GetByKeyAsync(string configKey);
|
|
||||||
Task<bool> DeleteByKeyAsync(string configKey);
|
|
||||||
Task<bool> KeyExistsAsync(string configKey);
|
|
||||||
Task<IEnumerable<SystemConfig>> GetByCategoryAsync(string category);
|
|
||||||
SystemConfig UpsertAsync(SystemConfig config);
|
|
||||||
Task<string> GetValueAsync(string configKey);
|
|
||||||
Task<bool> SetValueAsync(string configKey, string value);
|
|
||||||
Task<IEnumerable<SystemConfig>> GetActiveConfigsAsync();
|
|
||||||
Task<SystemConfig> GetDefaultConfigAsync();
|
|
||||||
Task<bool> UpdateConfigValueAsync(string configKey, string value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SystemConfigRepository : Repository<SystemConfig>, ISystemConfigRepository
|
|
||||||
{
|
|
||||||
private readonly CNCDbContext _context;
|
|
||||||
|
|
||||||
public SystemConfigRepository(CNCDbContext context) : base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SystemConfig> GetByKeyAsync(string configKey)
|
|
||||||
{
|
|
||||||
return await _context.SystemConfigs
|
|
||||||
.FirstOrDefaultAsync(c => c.ConfigKey == configKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> DeleteByKeyAsync(string configKey)
|
|
||||||
{
|
|
||||||
var config = await GetByKeyAsync(configKey);
|
|
||||||
if (config != null)
|
|
||||||
{
|
|
||||||
_context.SystemConfigs.Remove(config);
|
|
||||||
await SaveAsync();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> KeyExistsAsync(string configKey)
|
|
||||||
{
|
|
||||||
return await _context.SystemConfigs
|
|
||||||
.AnyAsync(c => c.ConfigKey == configKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<SystemConfig>> GetByCategoryAsync(string category)
|
|
||||||
{
|
|
||||||
return await _context.SystemConfigs
|
|
||||||
.Where(c => c.Category == category)
|
|
||||||
.OrderBy(c => c.ConfigKey)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SystemConfig UpsertAsync(SystemConfig config)
|
|
||||||
{
|
|
||||||
var existing = _context.SystemConfigs
|
|
||||||
.FirstOrDefault(c => c.ConfigKey == config.ConfigKey);
|
|
||||||
|
|
||||||
if (existing != null)
|
|
||||||
{
|
|
||||||
// Update existing
|
|
||||||
existing.ConfigValue = config.ConfigValue;
|
|
||||||
existing.Description = config.Description;
|
|
||||||
existing.UpdatedAt = DateTime.Now;
|
|
||||||
existing.IsActive = config.IsActive;
|
|
||||||
existing.Category = config.Category;
|
|
||||||
_context.SystemConfigs.Update(existing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Insert new
|
|
||||||
config.CreatedAt = DateTime.Now;
|
|
||||||
config.UpdatedAt = DateTime.Now;
|
|
||||||
_context.SystemConfigs.Add(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveAsync();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetValueAsync(string configKey)
|
|
||||||
{
|
|
||||||
var config = await GetByKeyAsync(configKey);
|
|
||||||
return config?.ConfigValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SetValueAsync(string configKey, string value)
|
|
||||||
{
|
|
||||||
var config = await GetByKeyAsync(configKey);
|
|
||||||
if (config != null)
|
|
||||||
{
|
|
||||||
config.ConfigValue = value;
|
|
||||||
config.UpdatedAt = DateTime.Now;
|
|
||||||
await SaveAsync();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<SystemConfig>> GetActiveConfigsAsync()
|
|
||||||
{
|
|
||||||
return await _context.SystemConfigs
|
|
||||||
.Where(c => c.IsActive)
|
|
||||||
.OrderBy(c => c.Category)
|
|
||||||
.ThenBy(c => c.ConfigKey)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SystemConfig> GetDefaultConfigAsync()
|
|
||||||
{
|
|
||||||
return await _context.SystemConfigs
|
|
||||||
.FirstOrDefaultAsync(c => c.IsDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> UpdateConfigValueAsync(string configKey, string value)
|
|
||||||
{
|
|
||||||
var config = await GetByKeyAsync(configKey);
|
|
||||||
if (config != null)
|
|
||||||
{
|
|
||||||
config.ConfigValue = value;
|
|
||||||
config.UpdatedAt = DateTime.Now;
|
|
||||||
await SaveAsync();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
|||||||
3e3908621d77d028c205325d4d43007864ecc556
|
4ed16a54e5ac1a1c4893fbacd380ecf37ee763c1
|
||||||
|
|||||||
@ -1,7 +1,2 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
|
||||||
<Import Project="$(NuGetPackageRoot)system.text.json/7.0.0/buildTransitive/net6.0/System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json/7.0.0/buildTransitive/net6.0/System.Text.Json.targets')" />
|
|
||||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/7.0.0/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/7.0.0/buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
|
|
||||||
</ImportGroup>
|
|
||||||
</Project>
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,81 +1,30 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"dgSpecHash": "n+xdRAhcNg3J/OdmEBMOpDsgnuQbCY8EtwKbUclpRKp0/OYy4jdcYs5E4vglBVc6KURjabQne39GuK3NzrCrLA==",
|
"dgSpecHash": "wQAfwmPHorUKjFxQTMXchTVaWeo2gGjdDVGsDBGyC9aQdwk+Xi/Xsvkvg1DidkHWGTb7MeD9JzhsNZATgutK6A==",
|
||||||
"success": true,
|
"success": true,
|
||||||
"projectFilePath": "/root/opencode/haoliang/Haoliang.Data/Haoliang.Data.csproj",
|
"projectFilePath": "/root/opencode/haoliang/Haoliang.Data/Haoliang.Data.csproj",
|
||||||
"expectedPackageFiles": [
|
"expectedPackageFiles": [
|
||||||
"/root/.nuget/packages/bcrypt.net-next/4.0.3/bcrypt.net-next.4.0.3.nupkg.sha512",
|
"/root/.nuget/packages/humanizer.core/2.8.26/humanizer.core.2.8.26.nupkg.sha512",
|
||||||
"/root/.nuget/packages/humanizer.core/2.14.1/humanizer.core.2.14.1.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.entityframeworkcore/6.0.0/microsoft.entityframeworkcore.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.authentication.abstractions/2.2.0/microsoft.aspnetcore.authentication.abstractions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.entityframeworkcore.abstractions/6.0.0/microsoft.entityframeworkcore.abstractions.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.authorization/2.2.0/microsoft.aspnetcore.authorization.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.entityframeworkcore.analyzers/6.0.0/microsoft.entityframeworkcore.analyzers.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.authorization.policy/2.2.0/microsoft.aspnetcore.authorization.policy.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.entityframeworkcore.design/6.0.0/microsoft.entityframeworkcore.design.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.connections.abstractions/2.2.0/microsoft.aspnetcore.connections.abstractions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.entityframeworkcore.relational/6.0.0/microsoft.entityframeworkcore.relational.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.hosting.abstractions/2.2.0/microsoft.aspnetcore.hosting.abstractions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.entityframeworkcore.tools/6.0.0/microsoft.entityframeworkcore.tools.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.hosting.server.abstractions/2.2.0/microsoft.aspnetcore.hosting.server.abstractions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.caching.abstractions/6.0.0/microsoft.extensions.caching.abstractions.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.http/2.2.0/microsoft.aspnetcore.http.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.caching.memory/6.0.0/microsoft.extensions.caching.memory.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.http.abstractions/2.2.0/microsoft.aspnetcore.http.abstractions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.configuration.abstractions/6.0.0/microsoft.extensions.configuration.abstractions.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.http.connections/1.1.0/microsoft.aspnetcore.http.connections.1.1.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.dependencyinjection/6.0.0/microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.http.connections.common/1.1.0/microsoft.aspnetcore.http.connections.common.1.1.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/6.0.0/microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.http.extensions/2.2.0/microsoft.aspnetcore.http.extensions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.logging/6.0.0/microsoft.extensions.logging.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.http.features/2.2.0/microsoft.aspnetcore.http.features.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.logging.abstractions/6.0.0/microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.routing/2.2.0/microsoft.aspnetcore.routing.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.options/6.0.0/microsoft.extensions.options.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.routing.abstractions/2.2.0/microsoft.aspnetcore.routing.abstractions.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/microsoft.extensions.primitives/6.0.0/microsoft.extensions.primitives.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.signalr/1.1.0/microsoft.aspnetcore.signalr.1.1.0.nupkg.sha512",
|
"/root/.nuget/packages/mysqlconnector/2.0.0/mysqlconnector.2.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.signalr.common/1.1.0/microsoft.aspnetcore.signalr.common.1.1.0.nupkg.sha512",
|
"/root/.nuget/packages/pomelo.entityframeworkcore.mysql/6.0.0/pomelo.entityframeworkcore.mysql.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.signalr.core/1.1.0/microsoft.aspnetcore.signalr.core.1.1.0.nupkg.sha512",
|
"/root/.nuget/packages/system.collections.immutable/6.0.0/system.collections.immutable.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.signalr.protocols.json/1.1.0/microsoft.aspnetcore.signalr.protocols.json.1.1.0.nupkg.sha512",
|
"/root/.nuget/packages/system.diagnostics.diagnosticsource/6.0.0/system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512",
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.websockets/2.2.0/microsoft.aspnetcore.websockets.2.2.0.nupkg.sha512",
|
"/root/.nuget/packages/system.runtime.compilerservices.unsafe/6.0.0/system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
|
||||||
"/root/.nuget/packages/microsoft.aspnetcore.webutilities/2.2.0/microsoft.aspnetcore.webutilities.2.2.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.csharp/4.5.0/microsoft.csharp.4.5.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.entityframeworkcore/7.0.2/microsoft.entityframeworkcore.7.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.entityframeworkcore.abstractions/7.0.2/microsoft.entityframeworkcore.abstractions.7.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.entityframeworkcore.analyzers/7.0.2/microsoft.entityframeworkcore.analyzers.7.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.entityframeworkcore.design/7.0.2/microsoft.entityframeworkcore.design.7.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.entityframeworkcore.relational/7.0.2/microsoft.entityframeworkcore.relational.7.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.entityframeworkcore.tools/7.0.2/microsoft.entityframeworkcore.tools.7.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.caching.abstractions/7.0.0/microsoft.extensions.caching.abstractions.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.caching.memory/7.0.0/microsoft.extensions.caching.memory.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.configuration.abstractions/7.0.0/microsoft.extensions.configuration.abstractions.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.dependencyinjection/7.0.0/microsoft.extensions.dependencyinjection.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/7.0.0/microsoft.extensions.dependencyinjection.abstractions.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.dependencymodel/7.0.0/microsoft.extensions.dependencymodel.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.fileproviders.abstractions/2.2.0/microsoft.extensions.fileproviders.abstractions.2.2.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.hosting.abstractions/2.2.0/microsoft.extensions.hosting.abstractions.2.2.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.logging/7.0.0/microsoft.extensions.logging.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.logging.abstractions/7.0.0/microsoft.extensions.logging.abstractions.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.objectpool/2.2.0/microsoft.extensions.objectpool.2.2.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.options/7.0.0/microsoft.extensions.options.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.extensions.primitives/7.0.0/microsoft.extensions.primitives.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.identitymodel.abstractions/6.26.0/microsoft.identitymodel.abstractions.6.26.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.identitymodel.jsonwebtokens/6.26.0/microsoft.identitymodel.jsonwebtokens.6.26.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.identitymodel.logging/6.26.0/microsoft.identitymodel.logging.6.26.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.identitymodel.tokens/6.26.0/microsoft.identitymodel.tokens.6.26.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.net.http.headers/2.2.0/microsoft.net.http.headers.2.2.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.netcore.platforms/2.0.0/microsoft.netcore.platforms.2.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/microsoft.netcore.targets/1.1.0/microsoft.netcore.targets.1.1.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/mono.texttemplating/2.2.1/mono.texttemplating.2.2.1.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/mysqlconnector/2.2.5/mysqlconnector.2.2.5.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/newtonsoft.json/11.0.2/newtonsoft.json.11.0.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/pomelo.entityframeworkcore.mysql/7.0.0/pomelo.entityframeworkcore.mysql.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.buffers/4.5.0/system.buffers.4.5.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.codedom/4.4.0/system.codedom.4.4.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.identitymodel.tokens.jwt/6.26.0/system.identitymodel.tokens.jwt.6.26.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.io/4.3.0/system.io.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.io.pipelines/4.5.2/system.io.pipelines.4.5.2.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.net.websockets.websocketprotocol/4.5.1/system.net.websockets.websocketprotocol.4.5.1.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.reflection/4.3.0/system.reflection.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.reflection.emit/4.3.0/system.reflection.emit.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.reflection.emit.ilgeneration/4.3.0/system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.reflection.primitives/4.3.0/system.reflection.primitives.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.runtime/4.3.0/system.runtime.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.runtime.compilerservices.unsafe/6.0.0/system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.security.cryptography.cng/4.5.0/system.security.cryptography.cng.4.5.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.security.principal.windows/4.5.0/system.security.principal.windows.4.5.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.text.encoding/4.3.0/system.text.encoding.4.3.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.text.encodings.web/7.0.0/system.text.encodings.web.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.text.json/7.0.0/system.text.json.7.0.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.threading.channels/4.5.0/system.threading.channels.4.5.0.nupkg.sha512",
|
|
||||||
"/root/.nuget/packages/system.threading.tasks/4.3.0/system.threading.tasks.4.3.0.nupkg.sha512"
|
|
||||||
],
|
],
|
||||||
"logs": []
|
"logs": []
|
||||||
}
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.System
|
||||||
|
{
|
||||||
|
public class LogEntry
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public string Level { get; set; }
|
||||||
|
public string Category { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string Exception { get; set; }
|
||||||
|
public string Source { get; set; }
|
||||||
|
public string DeviceId { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.Template
|
||||||
|
{
|
||||||
|
public class TagMapping
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int TemplateId { get; set; }
|
||||||
|
public string SourceFieldPath { get; set; }
|
||||||
|
public string StandardFieldId { get; set; }
|
||||||
|
public string StandardFieldDesc { get; set; }
|
||||||
|
public string DataType { get; set; }
|
||||||
|
public ConversionRule ConversionRule { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
|||||||
e646755fc76efe336c702b67be8a443e31ae6973
|
77a9315ff1d80b1bb2e72d8fd3baedd1d80436db
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "server=localhost;database=cnc_business;user=cnc_user;password=your_password;port=3306",
|
||||||
|
"BusinessDbConnection": "server=localhost;database=cnc_business;user=cnc_user;password=your_password;port=3306",
|
||||||
|
"LogDbConnection": "server=localhost;database=cnc_log;user=cnc_user;password=your_password;port=3306"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"AppSettings": {
|
||||||
|
"Name": "CNC Data Collection System",
|
||||||
|
"Version": "1.0.0",
|
||||||
|
"Environment": "Development",
|
||||||
|
"EnableSwagger": true,
|
||||||
|
"EnableSignalR": true,
|
||||||
|
"DefaultCollectionInterval": 60,
|
||||||
|
"MaxRetryAttempts": 3,
|
||||||
|
"RetryDelaySeconds": 30,
|
||||||
|
"DevicePingTimeoutSeconds": 5,
|
||||||
|
"DataRetentionDays": 90,
|
||||||
|
"EnableHealthChecks": true,
|
||||||
|
"EnableMetrics": true,
|
||||||
|
"JwtSettings": {
|
||||||
|
"SecretKey": "your_super_secret_jwt_key_here",
|
||||||
|
"Issuer": "CNC_System",
|
||||||
|
"Audience": "CNC_WebApp",
|
||||||
|
"ExpirationHours": 24
|
||||||
|
},
|
||||||
|
"EmailSettings": {
|
||||||
|
"SmtpServer": "smtp.gmail.com",
|
||||||
|
"SmtpPort": 587,
|
||||||
|
"Username": "your_email@gmail.com",
|
||||||
|
"Password": "your_app_password",
|
||||||
|
"UseSsl": true
|
||||||
|
},
|
||||||
|
"SmsSettings": {
|
||||||
|
"Provider": "Twilio",
|
||||||
|
"AccountSid": "your_account_sid",
|
||||||
|
"AuthToken": "your_auth_token",
|
||||||
|
"FromNumber": "+1234567890"
|
||||||
|
},
|
||||||
|
"WeChatSettings": {
|
||||||
|
"AppId": "your_wechat_app_id",
|
||||||
|
"AppSecret": "your_wechat_app_secret",
|
||||||
|
"AccessToken": "your_access_token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CollectionSettings": {
|
||||||
|
"Enabled": true,
|
||||||
|
"IntervalSeconds": 60,
|
||||||
|
"MaxRetryAttempts": 3,
|
||||||
|
"RetryDelaySeconds": 30,
|
||||||
|
"PingTimeoutSeconds": 5,
|
||||||
|
"DataRetentionDays": 90,
|
||||||
|
"ParallelCollections": 10,
|
||||||
|
"EnableCompression": true,
|
||||||
|
"BatchSize": 100
|
||||||
|
},
|
||||||
|
"ProductionSettings": {
|
||||||
|
"Enabled": true,
|
||||||
|
"CalculationIntervalMinutes": 5,
|
||||||
|
"AutoArchiveDays": 90,
|
||||||
|
"QualityCheckEnabled": true,
|
||||||
|
"AnomalyDetectionEnabled": true,
|
||||||
|
"ForecastDays": 7,
|
||||||
|
"OeeCalculationEnabled": true
|
||||||
|
},
|
||||||
|
"AlarmSettings": {
|
||||||
|
"Enabled": true,
|
||||||
|
"AutoResolveHours": 24,
|
||||||
|
"MaxActiveAlarms": 100,
|
||||||
|
"NotificationEnabled": true,
|
||||||
|
"EmailNotifications": true,
|
||||||
|
"SmsNotifications": false,
|
||||||
|
"WeChatNotifications": false,
|
||||||
|
"EscalationEnabled": true,
|
||||||
|
"EscalationIntervalMinutes": 30
|
||||||
|
},
|
||||||
|
"TemplateSettings": {
|
||||||
|
"Enabled": true,
|
||||||
|
"DefaultBrand": "FANUC",
|
||||||
|
"ValidationEnabled": true,
|
||||||
|
"AutoBackupEnabled": true,
|
||||||
|
"BackupIntervalDays": 7
|
||||||
|
},
|
||||||
|
"RealTimeSettings": {
|
||||||
|
"Enabled": true,
|
||||||
|
"UpdateIntervalSeconds": 30,
|
||||||
|
"MaxConnections": 1000,
|
||||||
|
"HeartbeatIntervalSeconds": 60,
|
||||||
|
"EnableCompression": true
|
||||||
|
},
|
||||||
|
"CacheSettings": {
|
||||||
|
"Enabled": true,
|
||||||
|
"Provider": "Memory",
|
||||||
|
"ExpirationMinutes": 30,
|
||||||
|
"SizeLimitMB": 512,
|
||||||
|
"SlidingExpiration": true
|
||||||
|
},
|
||||||
|
"SecuritySettings": {
|
||||||
|
"JwtExpirationHours": 24,
|
||||||
|
"PasswordRequireDigit": true,
|
||||||
|
"PasswordRequireLowercase": true,
|
||||||
|
"PasswordRequireUppercase": true,
|
||||||
|
"PasswordRequireNonalphanumeric": true,
|
||||||
|
"PasswordRequiredLength": 8,
|
||||||
|
"LoginAttemptLockout": true,
|
||||||
|
"MaxFailedLoginAttempts": 5,
|
||||||
|
"LockoutDurationMinutes": 15
|
||||||
|
},
|
||||||
|
"PerformanceSettings": {
|
||||||
|
"EnableMetrics": true,
|
||||||
|
"EnableProfiling": false,
|
||||||
|
"SlowQueryThresholdMs": 1000,
|
||||||
|
"EnableCompression": true,
|
||||||
|
"EnableCaching": true,
|
||||||
|
"BatchSize": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue