diff --git a/Haoliang.Api/Controllers/AuthController.cs b/Haoliang.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..f9e614a --- /dev/null +++ b/Haoliang.Api/Controllers/AuthController.cs @@ -0,0 +1,473 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Haoliang.Core.Services; +using Haoliang.Models.User; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Controllers +{ + [ApiController] + [Route("api/v1/[controller]")] + public class AuthController : ControllerBase + { + private readonly IAuthService _authService; + private readonly IUserService _userService; + private readonly IPermissionService _permissionService; + private readonly ILoggingService _loggingService; + + public AuthController( + IAuthService authService, + IUserService userService, + IPermissionService permissionService, + ILoggingService loggingService) + { + _authService = authService; + _userService = userService; + _permissionService = permissionService; + _loggingService = loggingService; + } + + [HttpPost("login")] + public async Task Login([FromBody] LoginRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ApiResponse.Error("Invalid request data", 400)); + } + + var result = await _authService.LoginAsync(request); + + if (result.Success) + { + await _loggingService.LogInformationAsync($"User {request.Username} logged in successfully"); + return Ok(ApiResponse.Success(result)); + } + else + { + await _loggingService.LogWarningAsync($"Failed login attempt for user {request.Username}"); + return Unauthorized(ApiResponse.Error("Invalid username or password", 401)); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Login error for user {request.Username}: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPost("logout")] + public async Task Logout([FromBody] LogoutRequest request) + { + try + { + if (request?.UserId == null) + { + return BadRequest(ApiResponse.Error("User ID is required", 400)); + } + + var success = await _authService.LogoutAsync(request.UserId.Value); + + if (success) + { + await _loggingService.LogInformationAsync($"User {request.UserId} logged out successfully"); + return Ok(ApiResponse.Success(true)); + } + else + { + return BadRequest(ApiResponse.Error("Logout failed", 400)); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Logout error: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPost("refresh")] + public async Task RefreshToken([FromBody] RefreshTokenRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ApiResponse.Error("Invalid request data", 400)); + } + + var result = await _authService.RefreshTokenAsync(request.RefreshToken); + + if (result.Success) + { + return Ok(ApiResponse.Success(result)); + } + else + { + return Unauthorized(ApiResponse.Error("Invalid refresh token", 401)); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Token refresh error: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPost("register")] + public async Task Register([FromBody] RegisterRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ApiResponse.Error("Invalid request data", 400)); + } + + // Check if username already exists + var usernameExists = await _authService.UsernameExistsAsync(request.Username); + if (usernameExists) + { + return BadRequest(ApiResponse.Error("Username already exists", 400)); + } + + // Check if email already exists + var emailExists = await _authService.EmailExistsAsync(request.Email); + if (emailExists) + { + return BadRequest(ApiResponse.Error("Email already exists", 400)); + } + + // Create user + var user = new User + { + Username = request.Username, + Email = request.Email, + PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password), + FirstName = request.FirstName, + LastName = request.LastName, + Department = request.Department, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + + var userViewModel = await _userService.CreateUserAsync(user); + + await _loggingService.LogInformationAsync($"New user registered: {request.Username}"); + return Ok(ApiResponse.Success(userViewModel)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Registration error for user {request.Username}: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpGet("profile")] + public async Task GetProfile() + { + try + { + // Get user ID from claims (in real implementation, use JWT token validation) + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(ApiResponse.Error("Not authenticated", 401)); + } + + var user = await _userService.GetUserByIdAsync(userId.Value); + if (user == null) + { + return NotFound(ApiResponse.Error("User not found", 404)); + } + + return Ok(ApiResponse.Success(user)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Profile fetch error: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPut("profile")] + public async Task UpdateProfile([FromBody] UpdateUserProfileRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ApiResponse.Error("Invalid request data", 400)); + } + + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(ApiResponse.Error("Not authenticated", 401)); + } + + var user = new User + { + Username = request.Username, + Email = request.Email, + FirstName = request.FirstName, + LastName = request.LastName, + Department = request.Department, + UpdatedAt = DateTime.Now + }; + + var updatedUser = await _userService.UpdateUserAsync(userId.Value, user); + + await _loggingService.LogInformationAsync($"User profile updated: {request.Username}"); + return Ok(ApiResponse.Success(updatedUser)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Profile update error: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPost("change-password")] + public async Task ChangePassword([FromBody] ChangePasswordRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ApiResponse.Error("Invalid request data", 400)); + } + + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(ApiResponse.Error("Not authenticated", 401)); + } + + var success = await _userService.ChangePasswordAsync( + userId.Value, + request.OldPassword, + request.NewPassword); + + if (success) + { + await _loggingService.LogInformationAsync($"Password changed for user: {userId}"); + return Ok(ApiResponse.Success(true)); + } + else + { + return BadRequest(ApiResponse.Error("Invalid old password", 400)); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Password change error: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpGet("permissions")] + public async Task GetUserPermissions() + { + try + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(ApiResponse>.Error("Not authenticated", 401)); + } + + var permissions = await _permissionService.GetUserPermissionsAsync(userId.Value); + return Ok(ApiResponse>.Success(permissions)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Permission fetch error: {ex.Message}", ex); + return StatusCode(500, ApiResponse>.Error("Internal server error", 500)); + } + } + + [HttpGet("users")] + [ProducesResponseType(typeof(PaginatedResponse), 200)] + public async Task GetAllUsers([FromQuery] UserFilter filter) + { + try + { + var users = await _userService.GetAllUsersAsync(); + + // Apply filters + if (!string.IsNullOrEmpty(filter.Role)) + { + users = users.Where(u => u.Role == filter.Role); + } + + if (!string.IsNullOrEmpty(filter.Department)) + { + users = users.Where(u => u.Department == filter.Department); + } + + if (filter.IsActive.HasValue) + { + users = users.Where(u => u.IsActive == filter.IsActive.Value); + } + + var totalCount = users.Count(); + + // Apply pagination + var pagedUsers = users + .Skip((filter.PageNumber - 1) * filter.PageSize) + .Take(filter.PageSize) + .ToList(); + + var response = new PaginatedResponse + { + Items = pagedUsers, + TotalCount = totalCount, + PageNumber = filter.PageNumber, + PageSize = filter.PageSize, + TotalPages = (int)Math.Ceiling((double)totalCount / filter.PageSize), + HasPreviousPage = filter.PageNumber > 1, + HasNextPage = filter.PageNumber < (int)Math.Ceiling((double)totalCount / filter.PageSize) + }; + + return Ok(response); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Users fetch error: {ex.Message}", ex); + return StatusCode(500, ApiResponse>.Error("Internal server error", 500)); + } + } + + [HttpGet("users/{id}")] + public async Task GetUserById(int id) + { + try + { + var user = await _userService.GetUserByIdAsync(id); + if (user == null) + { + return NotFound(ApiResponse.Error("User not found", 404)); + } + + return Ok(ApiResponse.Success(user)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"User fetch error for ID {id}: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPost("users/{id}/activate")] + public async Task ActivateUser(int id) + { + try + { + var success = await _userService.ActivateUserAsync(id); + if (success) + { + await _loggingService.LogInformationAsync($"User activated: {id}"); + return Ok(ApiResponse.Success(true)); + } + else + { + return BadRequest(ApiResponse.Error("Failed to activate user", 400)); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"User activation error for ID {id}: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + [HttpPost("users/{id}/deactivate")] + public async Task DeactivateUser(int id) + { + try + { + var success = await _userService.DeactivateUserAsync(id); + if (success) + { + await _loggingService.LogInformationAsync($"User deactivated: {id}"); + return Ok(ApiResponse.Success(true)); + } + else + { + return BadRequest(ApiResponse.Error("Failed to deactivate user", 400)); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"User deactivation error for ID {id}: {ex.Message}", ex); + return StatusCode(500, ApiResponse.Error("Internal server error", 500)); + } + } + + private int? GetCurrentUserId() + { + // In a real implementation, extract user ID from JWT token claims + // For now, return null (would be implemented in JWT middleware) + return null; + } + } + + // Supporting request and response models + public class LoginRequest + { + public string Username { get; set; } + public string Password { get; set; } + public bool RememberMe { get; set; } + } + + public class LogoutRequest + { + public int UserId { get; set; } + } + + public class RefreshTokenRequest + { + public string RefreshToken { get; set; } + } + + public class RegisterRequest + { + public string Username { get; set; } + public string Email { get; set; } + public string Password { get; set; } + public string ConfirmPassword { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Department { get; set; } + } + + public class UpdateUserProfileRequest + { + public string Username { get; set; } + public string Email { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Department { get; set; } + } + + public class ChangePasswordRequest + { + public string OldPassword { get; set; } + public string NewPassword { get; set; } + public string ConfirmPassword { get; set; } + } + + public class UserFilter + { + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; + public string Role { get; set; } + public string Department { get; set; } + public bool? IsActive { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Controllers/ConfigController.cs b/Haoliang.Api/Controllers/ConfigController.cs new file mode 100644 index 0000000..6d0df52 --- /dev/null +++ b/Haoliang.Api/Controllers/ConfigController.cs @@ -0,0 +1,515 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Haoliang.Core.Services; +using Haoliang.Models.Models.System; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Models.Template; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Controllers +{ + [Route("api/v1/config")] + [ApiController] + public class ConfigController : ControllerBase + { + private readonly ISystemConfigService _systemService; + private readonly ITemplateService _templateService; + private readonly IRulesService _rulesService; + private readonly IProductionStatisticsService _statisticsService; + + public ConfigController( + ISystemConfigService systemService, + ITemplateService templateService, + IRulesService rulesService, + IProductionStatisticsService statisticsService) + { + _systemService = systemService; + _templateService = templateService; + _rulesService = rulesService; + _statisticsService = statisticsService; + } + + /// + /// Get all system configuration + /// + [HttpGet] + public async Task>> GetSystemConfiguration() + { + try + { + var config = await _systemService.GetSystemConfigurationAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting system configuration: {ex.Message}")); + } + } + + /// + /// Update system configuration + /// + [HttpPut] + public async Task>> UpdateSystemConfiguration([FromBody] SystemConfiguration configuration) + { + try + { + var result = await _systemService.UpdateSystemConfigurationAsync(configuration); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating system configuration: {ex.Message}")); + } + } + + /// + /// Get production target configuration + /// + [HttpGet("production-targets")] + public async Task>>> GetProductionTargets() + { + try + { + var targets = await _systemService.GetProductionTargetsAsync(); + return Ok(ApiResponse>.Success(targets)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting production targets: {ex.Message}")); + } + } + + /// + /// Update production targets + /// + [HttpPut("production-targets")] + public async Task>> UpdateProductionTargets([FromBody] List targets) + { + try + { + var result = await _systemService.UpdateProductionTargetsAsync(targets); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating production targets: {ex.Message}")); + } + } + + /// + /// Get working hours configuration + /// + [HttpGet("working-hours")] + public async Task>> GetWorkingHoursConfig() + { + try + { + var config = await _systemService.GetWorkingHoursConfigAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting working hours config: {ex.Message}")); + } + } + + /// + /// Update working hours configuration + /// + [HttpPut("working-hours")] + public async Task>> UpdateWorkingHoursConfig([FromBody] WorkingHoursConfig config) + { + try + { + var result = await _systemService.UpdateWorkingHoursConfigAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating working hours config: {ex.Message}")); + } + } + + /// + /// Get alert configuration + /// + [HttpGet("alerts")] + public async Task>> GetAlertConfiguration() + { + try + { + var config = await _systemService.GetAlertConfigurationAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting alert configuration: {ex.Message}")); + } + } + + /// + /// Update alert configuration + /// + [HttpPut("alerts")] + public async Task>> UpdateAlertConfiguration([FromBody] AlertConfiguration config) + { + try + { + var result = await _systemService.UpdateAlertConfigurationAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating alert configuration: {ex.Message}")); + } + } + + /// + /// Get business rules configuration + /// + [HttpGet("rules")] + public async Task>>> GetBusinessRules() + { + try + { + var rules = await _rulesService.GetAllRulesAsync(); + return Ok(ApiResponse>.Success(rules)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting business rules: {ex.Message}")); + } + } + + /// + /// Create or update business rule + /// + [HttpPost("rules")] + public async Task>> CreateOrUpdateBusinessRule([FromBody] BusinessRuleConfig rule) + { + try + { + var result = await _rulesService.CreateOrUpdateRuleAsync(rule); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error creating/updating business rule: {ex.Message}")); + } + } + + /// + /// Delete business rule + /// + [HttpDelete("rules/{ruleId}")] + public async Task>> DeleteBusinessRule(int ruleId) + { + try + { + var result = await _rulesService.DeleteRuleAsync(ruleId); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error deleting business rule: {ex.Message}")); + } + } + + /// + /// Get statistical analysis rules + /// + [HttpGet("statistics-rules")] + public async Task>>> GetStatisticsRules() + { + try + { + var rules = await _rulesService.GetStatisticsRulesAsync(); + return Ok(ApiResponse>.Success(rules)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting statistics rules: {ex.Message}")); + } + } + + /// + /// Update statistics rules + /// + [HttpPut("statistics-rules")] + public async Task>> UpdateStatisticsRules([FromBody] List rules) + { + try + { + var result = await _rulesService.UpdateStatisticsRulesAsync(rules); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating statistics rules: {ex.Message}")); + } + } + + /// + /// Get data retention configuration + /// + [HttpGet("data-retention")] + public async Task>> GetDataRetentionConfig() + { + try + { + var config = await _systemService.GetDataRetentionConfigAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting data retention config: {ex.Message}")); + } + } + + /// + /// Update data retention configuration + /// + [HttpPut("data-retention")] + public async Task>> UpdateDataRetentionConfig([FromBody] DataRetentionConfig config) + { + try + { + var result = await _systemService.UpdateDataRetentionConfigAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating data retention config: {ex.Message}")); + } + } + + /// + /// Get dashboard configuration + /// + [HttpGet("dashboard")] + public async Task>> GetDashboardConfig() + { + try + { + var config = await _systemService.GetDashboardConfigAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting dashboard config: {ex.Message}")); + } + } + + /// + /// Update dashboard configuration + /// + [HttpPut("dashboard")] + public async Task>> UpdateDashboardConfig([FromBody] DashboardConfig config) + { + try + { + var result = await _systemService.UpdateDashboardConfigAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating dashboard config: {ex.Message}")); + } + } + + /// + /// Get export configuration + /// + [HttpGet("export")] + public async Task>> GetExportConfig() + { + try + { + var config = await _systemService.GetExportConfigAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting export config: {ex.Message}")); + } + } + + /// + /// Update export configuration + /// + [HttpPut("export")] + public async Task>> UpdateExportConfig([FromBody] ExportConfig config) + { + try + { + var result = await _systemService.UpdateExportConfigAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating export config: {ex.Message}")); + } + } + + /// + /// Get collection configuration + /// + [HttpGet("collection")] + public async Task>> GetCollectionConfig() + { + try + { + var config = await _systemService.GetCollectionConfigAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting collection config: {ex.Message}")); + } + } + + /// + /// Update collection configuration + /// + [HttpPut("collection")] + public async Task>> UpdateCollectionConfig([FromBody] CollectionConfig config) + { + try + { + var result = await _systemService.UpdateCollectionConfigAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating collection config: {ex.Message}")); + } + } + + /// + /// Get notification configuration + /// + [HttpGet("notifications")] + public async Task>> GetNotificationConfig() + { + try + { + var config = await _systemService.GetNotificationConfigAsync(); + return Ok(ApiResponse.Success(config)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting notification config: {ex.Message}")); + } + } + + /// + /// Update notification configuration + /// + [HttpPut("notifications")] + public async Task>> UpdateNotificationConfig([FromBody] NotificationConfig config) + { + try + { + var result = await _systemService.UpdateNotificationConfigAsync(config); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error updating notification config: {ex.Message}")); + } + } + + /// + /// Validate configuration + /// + [HttpPost("validate")] + public async Task>> ValidateConfiguration([FromBody] object configuration) + { + try + { + var result = await _systemService.ValidateConfigurationAsync(configuration); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error validating configuration: {ex.Message}")); + } + } + + /// + /// Export configuration + /// + [HttpGet("export")] + public async Task> ExportConfiguration() + { + try + { + var config = await _systemService.GetSystemConfigurationAsync(); + var json = System.Text.Json.JsonSerializer.Serialize(config, new System.Text.Json.JsonSerializerOptions + { + WriteIndented = true + }); + + return File(System.Text.Encoding.UTF8.GetBytes(json), "application/json", "system-configuration.json"); + } + catch (Exception ex) + { + return StatusCode(500, new { error = $"Error exporting configuration: {ex.Message}" }); + } + } + + /// + /// Import configuration + /// + [HttpPost("import")] + public async Task>> ImportConfiguration([FromBody] SystemConfiguration configuration) + { + try + { + var result = await _systemService.ImportConfigurationAsync(configuration); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error importing configuration: {ex.Message}")); + } + } + + /// + /// Reset to default configuration + /// + [HttpPost("reset")] + public async Task>> ResetToDefault() + { + try + { + var result = await _systemService.ResetToDefaultConfigurationAsync(); + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error resetting configuration: {ex.Message}")); + } + } + + /// + /// Get configuration change history + /// + [HttpGet("history")] + public async Task>>> GetConfigurationHistory([FromQuery] DateTime? startDate = null, [FromQuery] DateTime? endDate = null) + { + try + { + var history = await _systemService.GetConfigurationChangeHistoryAsync(startDate, endDate); + return Ok(ApiResponse>.Success(history)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting configuration history: {ex.Message}")); + } + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Controllers/RealTimeController.cs b/Haoliang.Api/Controllers/RealTimeController.cs new file mode 100644 index 0000000..256a0e4 --- /dev/null +++ b/Haoliang.Api/Controllers/RealTimeController.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Controllers +{ + [Route("api/v1/realtime")] + [ApiController] + public class RealTimeController : ControllerBase + { + private readonly IRealTimeService _realTimeService; + + public RealTimeController(IRealTimeService realTimeService) + { + _realTimeService = realTimeService; + } + + /// + /// Get connected clients count + /// + [HttpGet("clients/count")] + public async Task>> GetConnectedClientsCount() + { + try + { + var count = await _realTimeService.GetConnectedClientsCountAsync(); + return Ok(ApiResponse.Success(count)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting connected clients count: {ex.Message}")); + } + } + + /// + /// Get connected clients by type + /// + [HttpGet("clients/{clientType}")] + public async Task>>> GetConnectedClientsByType(string clientType) + { + try + { + var clients = await _realTimeService.GetConnectedClientsByTypeAsync(clientType); + return Ok(ApiResponse>.Success(clients)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting connected clients by type: {ex.Message}")); + } + } + + /// + /// Get device monitoring status + /// + [HttpGet("devices/{deviceId}/monitoring")] + public async Task>> GetDeviceMonitoringStatus(int deviceId) + { + try + { + var status = await _realTimeService.GetDeviceMonitoringStatusAsync(deviceId); + return Ok(ApiResponse.Success(status)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting device monitoring status: {ex.Message}")); + } + } + + /// + /// Start device streaming + /// + [HttpPost("devices/{deviceId}/streaming/start")] + public async Task>> StartDeviceStreaming( + int deviceId, + [FromQuery] int intervalMs = 1000) + { + try + { + await _realTimeService.StartDeviceStreamingAsync(deviceId, intervalMs); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error starting device streaming: {ex.Message}")); + } + } + + /// + /// Stop device streaming + /// + [HttpPost("devices/{deviceId}/streaming/stop")] + public async Task>> StopDeviceStreaming(int deviceId) + { + try + { + await _realTimeService.StopDeviceStreamingAsync(deviceId); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error stopping device streaming: {ex.Message}")); + } + } + + /// + /// Get active streaming devices + /// + [HttpGet("devices/streaming/active")] + public async Task>>> GetActiveStreamingDevices() + { + try + { + var devices = await _realTimeService.GetActiveStreamingDevicesAsync(); + return Ok(ApiResponse>.Success(devices)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting active streaming devices: {ex.Message}")); + } + } + + /// + /// Send test device status update + /// + [HttpPost("devices/{deviceId}/status")] + public async Task>> SendDeviceStatusUpdate( + int deviceId, + [FromBody] DeviceStatusUpdate statusUpdate) + { + try + { + statusUpdate.DeviceId = deviceId; + statusUpdate.Timestamp = DateTime.UtcNow; + + await _realTimeService.BroadcastDeviceStatusAsync(statusUpdate); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error sending device status update: {ex.Message}")); + } + } + + /// + /// Send test production update + /// + [HttpPost("devices/{deviceId}/production")] + public async Task>> SendProductionUpdate( + int deviceId, + [FromBody] ProductionUpdate productionUpdate) + { + try + { + productionUpdate.DeviceId = deviceId; + productionUpdate.Timestamp = DateTime.UtcNow; + + await _realTimeService.BroadcastProductionUpdateAsync(productionUpdate); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error sending production update: {ex.Message}")); + } + } + + /// + /// Send test alert + /// + [HttpPost("alerts")] + public async Task>> SendAlert([FromBody] AlertUpdate alertUpdate) + { + try + { + alertUpdate.Timestamp = DateTime.UtcNow; + await _realTimeService.BroadcastAlertAsync(alertUpdate); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error sending alert: {ex.Message}")); + } + } + + /// + /// Send system notification + /// + [HttpPost("notifications")] + public async Task>> SendSystemNotification([FromBody] SystemNotification notification) + { + try + { + notification.Timestamp = DateTime.UtcNow; + await _realTimeService.SendSystemNotificationAsync(notification); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error sending system notification: {ex.Message}")); + } + } + + /// + /// Send dashboard update + /// + [HttpPost("dashboard")] + public async Task>> SendDashboardUpdate([FromBody] DashboardUpdate dashboardUpdate) + { + try + { + dashboardUpdate.Timestamp = DateTime.UtcNow; + await _realTimeService.SendDashboardUpdateAsync(dashboardUpdate); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error sending dashboard update: {ex.Message}")); + } + } + + /// + /// Send command to specific client + /// + [HttpPost("clients/{connectionId}/command")] + public async Task>> SendCommandToClient( + string connectionId, + [FromBody] RealTimeCommand command) + { + try + { + command.Timestamp = DateTime.UtcNow; + await _realTimeService.SendCommandToClientAsync(connectionId, command); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error sending command to client: {ex.Message}")); + } + } + + /// + /// Broadcast command to all clients + /// + [HttpPost("command/broadcast")] + public async Task>> BroadcastCommand([FromBody] RealTimeCommand command) + { + try + { + command.Timestamp = DateTime.UtcNow; + await _realTimeService.BroadcastCommandAsync(command); + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error broadcasting command: {ex.Message}")); + } + } + + /// + /// Get WebSocket connection URL + /// + [HttpGet("connection-url")] + public ActionResult> GetConnectionUrl() + { + try + { + var baseUrl = $"{Request.Scheme}://{Request.Host}"; + var connectionUrl = $"{baseUrl}/realtimehub"; + + return Ok(ApiResponse.Success(connectionUrl)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting connection URL: {ex.Message}")); + } + } + + /// + /// Test WebSocket connectivity + /// + [HttpGet("test")] + public async Task>> TestWebSocket() + { + try + { + var testResult = new TestResult + { + TestId = Guid.NewGuid().ToString(), + Timestamp = DateTime.UtcNow, + ConnectedClients = await _realTimeService.GetConnectedClientsCountAsync(), + ActiveStreamingDevices = await _realTimeService.GetActiveStreamingDevicesAsync(), + Status = "Success" + }; + + return Ok(ApiResponse.Success(testResult)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error testing WebSocket: {ex.Message}")); + } + } + + /// + /// Get WebSocket statistics + /// + [HttpGet("statistics")] + public async Task>> GetWebSocketStatistics() + { + try + { + var connectedClients = await _realTimeService.GetConnectedClientsCountAsync(); + var streamingDevices = await _realTimeService.GetActiveStreamingDevicesAsync(); + + var stats = new WebSocketStatistics + { + Timestamp = DateTime.UtcNow, + ConnectedClients = connectedClients, + ActiveStreamingDevices = streamingDevices.Count, + TotalSessions = connectedClients, // Simplified + MessageCount = 0, // Would need to track this in the service + BytesTransferred = 0 // Would need to track this in the service + }; + + return Ok(ApiResponse.Success(stats)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting WebSocket statistics: {ex.Message}")); + } + } + + /// + /// Force refresh dashboard data + /// + [HttpPost("dashboard/refresh")] + public async Task>> RefreshDashboardData() + { + try + { + // This would trigger the dashboard update logic + // Implementation depends on specific requirements + return Ok(ApiResponse.Success(true)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error refreshing dashboard data: {ex.Message}")); + } + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Controllers/StatisticsController.cs b/Haoliang.Api/Controllers/StatisticsController.cs new file mode 100644 index 0000000..c498726 --- /dev/null +++ b/Haoliang.Api/Controllers/StatisticsController.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Haoliang.Core.Services; +using Haoliang.Models.Models.System; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Controllers +{ + [Route("api/v1/statistics")] + [ApiController] + public class StatisticsController : ControllerBase + { + private readonly IProductionStatisticsService _statisticsService; + + public StatisticsController(IProductionStatisticsService statisticsService) + { + _statisticsService = statisticsService; + } + + /// + /// Calculate production trends for a specific device and time range + /// + [HttpGet("production-trends")] + public async Task>> GetProductionTrends( + [FromQuery] int deviceId, + [FromQuery] DateTime startDate, + [FromQuery] DateTime endDate) + { + try + { + if (deviceId <= 0) + return BadRequest(ApiResponse.BadRequest("Invalid device ID")); + + if (startDate >= endDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var result = await _statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate); + + return Ok(ApiResponse.Success(result)); + } + catch (KeyNotFoundException ex) + { + return NotFound(ApiResponse.NotFound(ex.Message)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error calculating production trends: {ex.Message}")); + } + } + + /// + /// Generate comprehensive production report + /// + [HttpGet("production-report")] + public async Task>> GetProductionReport([FromQuery] ReportFilter filter) + { + try + { + if (filter.StartDate >= filter.EndDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var result = await _statisticsService.GenerateProductionReportAsync(filter); + + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error generating production report: {ex.Message}")); + } + } + + /// + /// Calculate efficiency metrics for devices or programs + /// + [HttpGet("efficiency")] + public async Task>> GetEfficiencyMetrics([FromQuery] EfficiencyFilter filter) + { + try + { + if (filter.StartDate >= filter.EndDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var result = await _statisticsService.CalculateEfficiencyMetricsAsync(filter); + + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error calculating efficiency metrics: {ex.Message}")); + } + } + + /// + /// Perform quality analysis based on production data + /// + [HttpGet("quality")] + public async Task>> GetQualityAnalysis([FromQuery] QualityFilter filter) + { + try + { + if (filter.StartDate >= filter.EndDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var result = await _statisticsService.PerformQualityAnalysisAsync(filter); + + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error performing quality analysis: {ex.Message}")); + } + } + + /// + /// Get production summary for dashboard display + /// + [HttpGet("dashboard-summary")] + public async Task>> GetDashboardSummary([FromQuery] DashboardFilter filter) + { + try + { + var result = await _statisticsService.GetDashboardSummaryAsync(filter); + + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting dashboard summary: {ex.Message}")); + } + } + + /// + /// Calculate OEE (Overall Equipment Effectiveness) for a specific device + /// + [HttpGet("oee")] + public async Task>> GetOeeMetrics( + [FromQuery] int deviceId, + [FromQuery] DateTime date) + { + try + { + if (deviceId <= 0) + return BadRequest(ApiResponse.BadRequest("Invalid device ID")); + + var result = await _statisticsService.CalculateOeeAsync(deviceId, date); + + return Ok(ApiResponse.Success(result)); + } + catch (KeyNotFoundException ex) + { + return NotFound(ApiResponse.NotFound(ex.Message)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error calculating OEE metrics: {ex.Message}")); + } + } + + /// + /// Get production forecasts based on historical data + /// + [HttpGet("forecast")] + public async Task>> GetProductionForecast([FromQuery] ForecastFilter filter) + { + try + { + if (filter.DeviceId <= 0) + return BadRequest(ApiResponse.BadRequest("Invalid device ID")); + + if (filter.DaysToForecast <= 0 || filter.DaysToForecast > 365) + return BadRequest(ApiResponse.BadRequest("Days to forecast must be between 1 and 365")); + + var result = await _statisticsService.GenerateProductionForecastAsync(filter); + + return Ok(ApiResponse.Success(result)); + } + catch (KeyNotFoundException ex) + { + return NotFound(ApiResponse.NotFound(ex.Message)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error generating production forecast: {ex.Message}")); + } + } + + /// + /// Detect production anomalies and outliers + /// + [HttpGet("anomalies")] + public async Task>> DetectProductionAnomalies([FromQuery] AnomalyFilter filter) + { + try + { + if (filter.StartDate >= filter.EndDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var result = await _statisticsService.DetectProductionAnomaliesAsync(filter); + + return Ok(ApiResponse.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error detecting production anomalies: {ex.Message}")); + } + } + + /// + /// Get available devices for statistics + /// + [HttpGet("devices")] + public async Task>>> GetAvailableDevices([FromQuery] bool activeOnly = true) + { + try + { + // This would typically get devices from device service + // For now, returning empty list + var result = new List(); + + return Ok(ApiResponse>.Success(result)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse>.InternalServerError($"Error getting available devices: {ex.Message}")); + } + } + + /// + /// Get production summary for multiple devices + /// + [HttpGet("multi-device-summary")] + public async Task>> GetMultiDeviceSummary([FromQuery] List deviceIds) + { + try + { + var filter = new DashboardFilter + { + DeviceIds = deviceIds, + Date = DateTime.Today, + IncludeAlerts = true + }; + + var dashboardSummary = await _statisticsService.GetDashboardSummaryAsync(filter); + + var multiDeviceSummary = new MultiDeviceSummary + { + GeneratedAt = dashboardSummary.GeneratedAt, + DeviceCount = dashboardSummary.TotalDevices, + ActiveDeviceCount = dashboardSummary.ActiveDevices, + OfflineDeviceCount = dashboardSummary.OfflineDevices, + TotalProductionToday = dashboardSummary.TotalProductionToday, + TotalProductionThisWeek = dashboardSummary.TotalProductionThisWeek, + TotalProductionThisMonth = dashboardSummary.TotalProductionThisMonth, + OverallEfficiency = dashboardSummary.OverallEfficiency, + OverallQualityRate = dashboardSummary.QualityRate, + DeviceSummaries = dashboardSummary.DeviceSummaries + }; + + return Ok(ApiResponse.Success(multiDeviceSummary)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting multi-device summary: {ex.Message}")); + } + } + + /// + /// Get historical production data for charting + /// + [HttpGet("historical-data")] + public async Task>> GetHistoricalProductionData( + [FromQuery] int deviceId, + [FromQuery] DateTime startDate, + [FromQuery] DateTime endDate, + [FromQuery] GroupBy groupBy = GroupBy.Date) + { + try + { + if (deviceId <= 0) + return BadRequest(ApiResponse.BadRequest("Invalid device ID")); + + if (startDate >= endDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var filter = new ReportFilter + { + DeviceIds = new List { deviceId }, + StartDate = startDate, + EndDate = endDate, + GroupBy = groupBy + }; + + var report = await _statisticsService.GenerateProductionReportAsync(filter); + + var historicalData = new HistoricalProductionData + { + DeviceId = deviceId, + PeriodStart = startDate, + PeriodEnd = endDate, + GroupBy = groupBy, + DataPoints = report.SummaryItems.Select(item => new DataPoint + { + Timestamp = groupBy == GroupBy.Date ? item.Date : + groupBy == GroupBy.Hour ? item.Hour : + item.Date, + Value = item.Quantity, + Target = item.TargetQuantity, + Efficiency = item.Efficiency + }).ToList() + }; + + return Ok(ApiResponse.Success(historicalData)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting historical production data: {ex.Message}")); + } + } + + /// + /// Get production efficiency trends over time + /// + [HttpGet("efficiency-trends")] + public async Task>> GetEfficiencyTrends( + [FromQuery] int deviceId, + [FromQuery] DateTime startDate, + [FromQuery] DateTime endDate, + [FromQuery] EfficiencyMetric metric = EfficiencyMetric.Oee) + { + try + { + if (deviceId <= 0) + return BadRequest(ApiResponse.BadRequest("Invalid device ID")); + + if (startDate >= endDate) + return BadRequest(ApiResponse.BadRequest("Start date must be before end date")); + + var filter = new EfficiencyFilter + { + DeviceIds = new List { deviceId }, + StartDate = startDate, + EndDate = endDate, + Metrics = metric + }; + + var efficiencyMetrics = await _statisticsService.CalculateEfficiencyMetricsAsync(filter); + + var trendData = new EfficiencyTrendData + { + DeviceId = deviceId, + Metric = metric, + PeriodStart = startDate, + PeriodEnd = endDate, + DataPoints = efficiencyMetrics.HourlyData.Select(point => new EfficiencyDataPoint + { + Timestamp = point.Hour, + Availability = point.Availability, + Performance = point.Performance, + Quality = point.Quality, + Oee = point.Oee + }).ToList() + }; + + return Ok(ApiResponse.Success(trendData)); + } + catch (Exception ex) + { + return StatusCode(500, ApiResponse.InternalServerError($"Error getting efficiency trends: {ex.Message}")); + } + } + } + + // Supporting models for API responses + public class MultiDeviceSummary + { + public DateTime GeneratedAt { get; set; } + public int DeviceCount { get; set; } + public int ActiveDeviceCount { get; set; } + public int OfflineDeviceCount { get; set; } + public decimal TotalProductionToday { get; set; } + public decimal TotalProductionThisWeek { get; set; } + public decimal TotalProductionThisMonth { get; set; } + public decimal OverallEfficiency { get; set; } + public decimal OverallQualityRate { get; set; } + public List DeviceSummaries { get; set; } + } + + public class HistoricalProductionData + { + public int DeviceId { get; set; } + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + public GroupBy GroupBy { get; set; } + public List DataPoints { get; set; } + } + + public class DataPoint + { + public DateTime Timestamp { get; set; } + public decimal Value { get; set; } + public decimal Target { get; set; } + public decimal Efficiency { get; set; } + } + + public class EfficiencyTrendData + { + public int DeviceId { get; set; } + public EfficiencyMetric Metric { get; set; } + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + public List DataPoints { get; set; } + } + + public class EfficiencyDataPoint + { + public DateTime Timestamp { get; set; } + public decimal Availability { get; set; } + public decimal Performance { get; set; } + public decimal Quality { get; set; } + public decimal Oee { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Filters/GlobalExceptionFilter.cs b/Haoliang.Api/Filters/GlobalExceptionFilter.cs new file mode 100644 index 0000000..e72a4c5 --- /dev/null +++ b/Haoliang.Api/Filters/GlobalExceptionFilter.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Filters +{ + public class GlobalExceptionFilter : IExceptionFilter + { + private readonly ILogger _logger; + private readonly ILoggingService _loggingService; + + public GlobalExceptionFilter(ILogger logger, ILoggingService loggingService) + { + _logger = logger; + _loggingService = loggingService; + } + + public void OnException(ExceptionContext context) + { + // Log the exception + _logger.LogError(context.Exception, "An unhandled exception occurred"); + await _loggingService.LogErrorAsync($"Unhandled exception: {context.Exception.Message}", context.Exception); + + // Handle specific exception types + if (context.Exception is ValidationException) + { + context.Result = CreateValidationResult(context.Exception as ValidationException); + context.ExceptionHandled = true; + return; + } + + if (context.Exception is NotFoundException) + { + context.Result = new NotFoundObjectResult(CreateErrorResponse("Resource not found", 404)); + context.ExceptionHandled = true; + return; + } + + if (context.Exception is ForbiddenException) + { + context.Result = new ObjectResult(CreateErrorResponse("Access forbidden", 403)) + { + StatusCode = 403 + }; + context.ExceptionHandled = true; + return; + } + + if (context.Exception is BadRequestException) + { + context.Result = new BadRequestObjectResult(CreateErrorResponse("Bad request", 400)); + context.ExceptionHandled = true; + return; + } + + // Handle model state validation errors + if (!context.ModelState.IsValid) + { + context.Result = new BadRequestObjectResult(CreateValidationErrorResponse(context.ModelState)); + context.ExceptionHandled = true; + return; + } + + // Default handling for unhandled exceptions + context.Result = new ObjectResult(CreateErrorResponse("An unexpected error occurred", 500)) + { + StatusCode = 500 + }; + context.ExceptionHandled = true; + } + + private IActionResult CreateValidationResult(ValidationException validationException) + { + var response = new ApiResponse> + { + Success = false, + Message = "Validation failed", + ErrorCode = 400, + Data = validationException?.Errors as Dictionary ?? new Dictionary(), + Timestamp = DateTime.Now + }; + + return new BadRequestObjectResult(response); + } + + private object CreateErrorResponse(string message, int errorCode) + { + return new ApiResponse + { + Success = false, + Message = message, + ErrorCode = errorCode, + Timestamp = DateTime.Now + }; + } + + private object CreateValidationErrorResponse(ModelStateDictionary modelState) + { + var errors = new Dictionary(); + + foreach (var keyModelStatePair in modelState) + { + var key = keyModelStatePair.Key; + var errorsArray = keyModelStatePair.Value.Errors.Select(error => error.ErrorMessage).ToArray(); + + if (errorsArray.Length > 0) + { + errors[key] = errorsArray; + } + } + + return new ApiResponse> + { + Success = false, + Message = "Validation failed", + ErrorCode = 400, + Data = errors, + Timestamp = DateTime.Now + }; + } + } + + public class ModelStateValidationFilter : IActionFilter + { + private readonly ILogger _logger; + + public ModelStateValidationFilter(ILogger logger) + { + _logger = logger; + } + + public void OnActionExecuting(ActionExecutingContext context) + { + if (!context.ModelState.IsValid) + { + var errors = new Dictionary(); + + foreach (var keyModelStatePair in context.ModelState) + { + var key = keyModelStatePair.Key; + var errorsArray = keyModelStatePair.Value.Errors.Select(error => error.ErrorMessage).ToArray(); + + if (errorsArray.Length > 0) + { + errors[key] = errorsArray; + } + } + + _logger.LogWarning($"Model validation failed: {JsonSerializer.Serialize(errors)}"); + + context.Result = new BadRequestObjectResult(new ApiResponse> + { + Success = false, + Message = "Validation failed", + ErrorCode = 400, + Data = errors, + Timestamp = DateTime.Now + }); + } + } + + public void OnActionExecuted(ActionExecutedContext context) + { + // No operation needed + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Filters/JwtAuthorizeFilter.cs b/Haoliang.Api/Filters/JwtAuthorizeFilter.cs new file mode 100644 index 0000000..a8b968a --- /dev/null +++ b/Haoliang.Api/Filters/JwtAuthorizeFilter.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Filters +{ + public class JwtAuthorizeFilter : IAuthorizationFilter + { + private readonly IAuthService _authService; + private readonly IPermissionService _permissionService; + + public JwtAuthorizeFilter(IAuthService authService, IPermissionService permissionService) + { + _authService = authService; + _permissionService = permissionService; + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType().Any(); + if (allowAnonymous) + return; + + var user = context.HttpContext.Items["User"] as User; + if (user == null) + { + // No user found in context, authentication failed + context.Result = new UnauthorizedResult(); + return; + } + + // Check if user is active + if (!user.IsActive) + { + context.Result = new ForbidResult(); + return; + } + + // Check for required permissions if specified + var requiredPermissions = context.ActionDescriptor.EndpointMetadata + .OfType() + .FirstOrDefault(); + + if (requiredPermissions != null) + { + var hasPermission = _permissionService.UserHasPermissionAsync(user.Id, requiredPermissions.PermissionName).Result; + if (!hasPermission) + { + context.Result = new ForbidResult(); + return; + } + } + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] + public class RequiredPermissionAttribute : Attribute + { + public string PermissionName { get; } + + public RequiredPermissionAttribute(string permissionName) + { + PermissionName = permissionName; + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class AllowAnonymousAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Haoliang.Api/Filters/ValidationFilter.cs b/Haoliang.Api/Filters/ValidationFilter.cs new file mode 100644 index 0000000..7d9585d --- /dev/null +++ b/Haoliang.Api/Filters/ValidationFilter.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Filters +{ + public class ValidationFilter : IActionFilter + { + private readonly ILoggingService _loggingService; + + public ValidationFilter(ILoggingService loggingService) + { + _loggingService = loggingService; + } + + public void OnActionExecuting(ActionExecutingContext context) + { + // Check if model state is valid + if (!context.ModelState.IsValid) + { + LogValidationErrors(context.ModelState); + context.Result = CreateValidationErrorResponse(context.ModelState); + } + } + + public void OnActionExecuted(ActionExecutedContext context) + { + // No operation needed + } + + private void LogValidationErrors(ModelStateDictionary modelState) + { + var errors = new Dictionary>(); + + foreach (var keyModelStatePair in modelState) + { + var key = keyModelStatePair.Key; + var modelStateErrors = keyModelStatePair.Value.Errors; + + if (modelStateErrors.Count > 0) + { + errors[key] = modelStateErrors.Select(error => + string.IsNullOrEmpty(error.ErrorMessage) ? "Validation error occurred" : error.ErrorMessage + ).ToList(); + } + } + + _loggingService.LogWarningAsync($"Model validation failed: {JsonSerializer.Serialize(errors)}").Wait(); + } + + private IActionResult CreateValidationErrorResponse(ModelStateDictionary modelState) + { + var errors = new Dictionary(); + + foreach (var keyModelStatePair in modelState) + { + var key = keyModelStatePair.Key; + var errorsArray = keyModelStatePair.Value.Errors.Select(error => + string.IsNullOrEmpty(error.ErrorMessage) ? "Validation error occurred" : error.ErrorMessage + ).ToArray(); + + if (errorsArray.Length > 0) + { + errors[key] = errorsArray; + } + } + + var response = new ApiResponse> + { + Success = false, + Message = "Validation failed", + ErrorCode = 400, + Data = errors, + Timestamp = DateTime.Now + }; + + return new BadRequestObjectResult(response); + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class ValidateModelAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + if (!context.ModelState.IsValid) + { + var errors = new Dictionary(); + + foreach (var keyModelStatePair in context.ModelState) + { + var key = keyModelStatePair.Key; + var errorsArray = keyModelStatePair.Value.Errors.Select(error => + string.IsNullOrEmpty(error.ErrorMessage) ? "Validation error occurred" : error.ErrorMessage + ).ToArray(); + + if (errorsArray.Length > 0) + { + errors[key] = errorsArray; + } + } + + var response = new ApiResponse> + { + Success = false, + Message = "Validation failed", + ErrorCode = 400, + Data = errors, + Timestamp = DateTime.Now + }; + + context.Result = new BadRequestObjectResult(response); + } + + base.OnActionExecuting(context); + } + } + + public class ValidationHelper + { + public static (bool IsValid, List Errors) ValidateModel(object model) + { + var validationContext = new ValidationContext(model); + var validationResults = new List(); + var isValid = Validator.TryValidateObject(model, validationContext, validationResults, true); + + var errors = validationResults.Select(vr => vr.ErrorMessage).ToList(); + + return (isValid, errors); + } + + public static (bool IsValid, List Errors) ValidateDictionary(Dictionary data, Dictionary expectedTypes) + { + var errors = new List(); + var isValid = true; + + foreach (var kvp in expectedTypes) + { + if (!data.ContainsKey(kvp.Key)) + { + errors.Add($"Missing required field: {kvp.Key}"); + isValid = false; + continue; + } + + var value = data[kvp.Key]; + var expectedType = kvp.Value; + + if (value == null) + { + if (expectedType == typeof(string)) + { + errors.Add($"Field {kvp.Key} cannot be empty"); + isValid = false; + } + continue; + } + + if (!expectedType.IsAssignableFrom(value.GetType())) + { + errors.Add($"Field {kvp.Key} must be of type {expectedType.Name}, got {value.GetType().Name}"); + isValid = false; + } + } + + return (isValid, errors); + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Haoliang.Api.csproj b/Haoliang.Api/Haoliang.Api.csproj index 8f8b936..78da64e 100644 --- a/Haoliang.Api/Haoliang.Api.csproj +++ b/Haoliang.Api/Haoliang.Api.csproj @@ -7,18 +7,26 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + + + + + + + diff --git a/Haoliang.Api/Hubs/RealTimeHub.cs b/Haoliang.Api/Hubs/RealTimeHub.cs new file mode 100644 index 0000000..68ad6b6 --- /dev/null +++ b/Haoliang.Api/Hubs/RealTimeHub.cs @@ -0,0 +1,547 @@ +using Microsoft.AspNetCore.SignalR; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Haoliang.Core.Services; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Models.System; + +namespace Haoliang.Api.Hubs +{ + public class RealTimeHub : Hub + { + private static readonly ConcurrentDictionary _connectedClients = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary _deviceStreaming = + new ConcurrentDictionary(); + + private readonly IRealTimeService _realTimeService; + private readonly IDeviceCollectionService _deviceCollectionService; + private readonly IProductionService _productionService; + + public RealTimeHub( + IRealTimeService realTimeService, + IDeviceCollectionService deviceCollectionService, + IProductionService productionService) + { + _realTimeService = realTimeService; + _deviceCollectionService = deviceCollectionService; + _productionService = productionService; + } + + /// + /// Called when a new client connects to the hub + /// + public override async Task OnConnectedAsync() + { + var connectionId = Context.ConnectionId; + + // Get client information from query parameters + var userId = Context.GetHttpContext().Request.Query["userId"]; + var clientType = Context.GetHttpContext().Request.Query["clientType"] ?? "web"; + var dashboardId = Context.GetHttpContext().Request.Query["dashboardId"]; + + var clientInfo = new ClientConnectionInfo + { + ConnectionId = connectionId, + UserId = userId.ToString(), + ClientType = clientType.ToString(), + ConnectedAt = DateTime.UtcNow, + LastActivity = DateTime.UtcNow, + DashboardId = string.IsNullOrEmpty(dashboardId.ToString()) ? null : dashboardId.ToString(), + UserAgent = Context.GetHttpContext().Request.Headers["User-Agent"].ToString(), + IpAddress = Context.GetHttpContext().Connection.RemoteIpAddress?.ToString() + }; + + _connectedClients.AddOrUpdate(connectionId, clientInfo, (key, existing) => clientInfo); + + // Add to notifications group by default + await Groups.AddToGroupAsync(connectionId, "notifications"); + + // If dashboard ID provided, add to dashboard group + if (!string.IsNullOrEmpty(clientInfo.DashboardId)) + { + await Groups.AddToGroupAsync(connectionId, $"dashboard_{clientInfo.DashboardId}"); + } + + // Notify other clients about new connection + await Clients.Others.SendAsync("ClientConnected", new + { + ClientId = connectionId, + UserId = clientInfo.UserId, + ClientType = clientType, + Timestamp = DateTime.UtcNow + }); + + // Send welcome message to connecting client + await Clients.Caller.SendAsync("Welcome", new + { + ClientId = connectionId, + Timestamp = DateTime.UtcNow, + ServerTime = DateTime.UtcNow + }); + + await base.OnConnectedAsync(); + } + + /// + /// Called when a client disconnects from the hub + /// + public override async Task OnDisconnectedAsync(Exception exception) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryRemove(connectionId, out var clientInfo)) + { + // Remove from all groups + await Groups.RemoveFromGroupAsync(connectionId, "notifications"); + await Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{clientInfo.DashboardId}"); + + // Remove from device groups + foreach (var deviceId in clientInfo.MonitoredDevices) + { + await Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}"); + } + + // Stop device streaming if client was streaming + foreach (var deviceId in clientInfo.StreamingDevices) + { + await StopDeviceStreamingInternal(deviceId); + } + + // Notify other clients about disconnection + await Clients.Others.SendAsync("ClientDisconnected", new + { + ClientId = connectionId, + UserId = clientInfo.UserId, + Reason = exception?.Message ?? "Unknown", + Timestamp = DateTime.UtcNow + }); + } + + await base.OnDisconnectedAsync(exception); + } + + /// + /// Client requests to join device monitoring group + /// + public async Task JoinDeviceGroup(int deviceId) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.MonitoredDevices.Add(deviceId); + clientInfo.LastActivity = DateTime.UtcNow; + + await Groups.AddToGroupAsync(connectionId, $"device_{deviceId}"); + + // Send current device status + var deviceStatus = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId); + await Clients.Caller.SendAsync("DeviceStatusUpdated", new + { + DeviceId = deviceId, + Status = deviceStatus.Status, + CurrentProgram = deviceStatus.CurrentProgram, + Runtime = deviceStatus.Runtime, + Timestamp = DateTime.UtcNow + }); + + // Notify other clients + await Clients.Group($"device_{deviceId}").SendAsync("DeviceMonitoringStarted", new + { + DeviceId = deviceId, + ClientCount = GetDeviceClientCount(deviceId), + Timestamp = DateTime.UtcNow + }); + } + } + + /// + /// Client requests to leave device monitoring group + /// + public async Task LeaveDeviceGroup(int deviceId) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.MonitoredDevices.Remove(deviceId); + await Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}"); + + // Notify other clients + await Clients.Group($"device_{deviceId}").SendAsync("DeviceMonitoringStopped", new + { + DeviceId = deviceId, + ClientCount = GetDeviceClientCount(deviceId), + Timestamp = DateTime.UtcNow + }); + } + } + + /// + /// Client requests to join dashboard group + /// + public async Task JoinDashboardGroup(string dashboardId) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.DashboardId = dashboardId; + clientInfo.LastActivity = DateTime.UtcNow; + await Groups.AddToGroupAsync(connectionId, $"dashboard_{dashboardId}"); + + // Send current dashboard data + var dashboardUpdate = await GetDashboardUpdateAsync(); + await Clients.Caller.SendAsync("DashboardUpdated", dashboardUpdate); + + // Notify dashboard group about new client + await Clients.Group($"dashboard_{dashboardId}").SendAsync("DashboardClientJoined", new + { + ClientId = connectionId, + DashboardId = dashboardId, + Timestamp = DateTime.UtcNow + }); + } + } + + /// + /// Client requests to leave dashboard group + /// + public async Task LeaveDashboardGroup(string dashboardId) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.DashboardId = null; + await Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{dashboardId}"); + + // Notify dashboard group about client leaving + await Clients.Group($"dashboard_{dashboardId}").SendAsync("DashboardClientLeft", new + { + ClientId = connectionId, + DashboardId = dashboardId, + Timestamp = DateTime.UtcNow + }); + } + } + + /// + /// Client requests to start device streaming + /// + public async Task StartDeviceStreaming(int deviceId, [FromQuery] int intervalMs = 1000) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.StreamingDevices.Add(deviceId); + clientInfo.LastActivity = DateTime.UtcNow; + + var streamingInfo = new DeviceStreamingInfo + { + DeviceId = deviceId, + IntervalMs = intervalMs, + StartedAt = DateTime.UtcNow, + LastUpdate = DateTime.UtcNow, + ClientsStreaming = new HashSet { connectionId } + }; + + _deviceStreaming.AddOrUpdate(deviceId, streamingInfo, (key, existing) => + { + existing.ClientsStreaming.Add(connectionId); + return existing; + }); + + // Start streaming task if not already running + if (!_deviceStreaming.ContainsKey(deviceId) || + _deviceStreaming[deviceId].ClientsStreaming.Count == 1) + { + await StartDeviceDataStream(deviceId, intervalMs); + } + + await Clients.Caller.SendAsync("DeviceStreamingStarted", new + { + DeviceId = deviceId, + IntervalMs = intervalMs, + Timestamp = DateTime.UtcNow + }); + } + } + + /// + /// Client requests to stop device streaming + /// + public async Task StopDeviceStreaming(int deviceId) + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.StreamingDevices.Remove(deviceId); + + await StopDeviceStreamingInternal(deviceId); + + await Clients.Caller.SendAsync("DeviceStreamingStopped", new + { + DeviceId = deviceId, + Timestamp = DateTime.UtcNow + }); + } + } + + /// + /// Client requests to join alerts group + /// + public async Task JoinAlertsGroup() + { + var connectionId = Context.ConnectionId; + await Groups.AddToGroupAsync(connectionId, "alerts"); + await Clients.Caller.SendAsync("JoinedAlertsGroup", new + { + Timestamp = DateTime.UtcNow + }); + } + + /// + /// Client requests to leave alerts group + /// + public async Task LeaveAlertsGroup() + { + var connectionId = Context.ConnectionId; + await Groups.RemoveFromGroupAsync(connectionId, "alerts"); + await Clients.Caller.SendAsync("LeftAlertsGroup", new + { + Timestamp = DateTime.UtcNow + }); + } + + /// + /// Client sends ping to keep connection alive + /// + public async Task Ping() + { + var connectionId = Context.ConnectionId; + + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) + { + clientInfo.LastActivity = DateTime.UtcNow; + } + + await Clients.Caller.SendAsync("Pong", new + { + Timestamp = DateTime.UtcNow + }); + } + + /// + /// Client requests system information + /// + public async Task GetSystemInfo() + { + var systemInfo = new + { + Timestamp = DateTime.UtcNow, + ServerTime = DateTime.UtcNow, + Uptime = DateTime.UtcNow, + Version = "1.0.0", // This would come from app settings + ConnectedClients = _connectedClients.Count, + StreamingDevices = _deviceStreaming.Count + }; + + await Clients.Caller.SendAsync("SystemInfo", systemInfo); + } + + /// + /// Client requests client list + /// + public async Task GetClientList() + { + var clients = _connectedClients.Values.Select(c => new + { + c.ConnectionId, + c.UserId, + c.ClientType, + c.ConnectedAt, + c.LastActivity, + c.DashboardId, + MonitoredDevices = c.MonitoredDevices.ToList(), + StreamingDevices = c.StreamingDevices.ToList() + }).ToList(); + + await Clients.Caller.SendAsync("ClientList", clients); + } + + #region Private Methods + + private async Task StartDeviceDataStream(int deviceId, int intervalMs) + { + try + { + while (_deviceStreaming.TryGetValue(deviceId, out var streamingInfo) && streamingInfo.ClientsStreaming.Any()) + { + try + { + // Get current device status + var deviceStatus = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId); + + // Get current production data + var production = await _productionService.GetDeviceProductionForDateAsync(deviceId, DateTime.Today); + + // Create streaming message + var streamingMessage = new DeviceStreamingMessage + { + DeviceId = deviceId, + DeviceName = deviceStatus.DeviceName, + Status = deviceStatus.Status, + CurrentProgram = deviceStatus.CurrentProgram, + Runtime = deviceStatus.Runtime, + Quantity = production, + Timestamp = DateTime.UtcNow, + IntervalMs = intervalMs + }; + + // Send to device group + await Clients.Group($"device_{deviceId}").SendAsync("DeviceStreamingData", streamingMessage); + + // Update last streaming time + streamingInfo.LastUpdate = DateTime.UtcNow; + } + catch (Exception ex) + { + // Log error but continue streaming + await Clients.Caller.SendAsync("StreamingError", new + { + DeviceId = deviceId, + ErrorMessage = ex.Message, + Timestamp = DateTime.UtcNow + }); + } + + await Task.Delay(intervalMs); + } + } + catch (Exception ex) + { + // Log fatal error + Console.WriteLine($"Device streaming task for device {deviceId} failed: {ex.Message}"); + } + } + + private async Task StopDeviceStreamingInternal(int deviceId) + { + if (_deviceStreaming.TryGetValue(deviceId, out var streamingInfo)) + { + var connectionId = Context.ConnectionId; + + streamingInfo.ClientsStreaming.Remove(connectionId); + + if (!streamingInfo.ClientsStreaming.Any()) + { + // No more clients streaming, remove from dictionary + _deviceStreaming.TryRemove(deviceId, out _); + } + else + { + // Update the streaming info + _deviceStreaming.AddOrUpdate(deviceId, streamingInfo, (key, existing) => streamingInfo); + } + } + } + + private async Task GetDashboardUpdateAsync() + { + // This would typically call the production service to get current dashboard data + // For now, returning a simplified version + var date = DateTime.Today; + + return new DashboardUpdate + { + Timestamp = DateTime.UtcNow, + TotalDevices = 10, // Placeholder + ActiveDevices = 8, // Placeholder + OfflineDevices = 2, // Placeholder + TotalProductionToday = 1250, // Placeholder + TotalProductionThisWeek = 8750, // Placeholder + TotalProductionThisMonth = 35000, // Placeholder + OverallEfficiency = 85.5m, // Placeholder + QualityRate = 98.2m, // Placeholder + DeviceSummaries = new List() // Placeholder + }; + } + + private int GetDeviceClientCount(int deviceId) + { + return _connectedClients.Values.Count(c => c.MonitoredDevices.Contains(deviceId)); + } + + #endregion + } + + #region Supporting Classes + + public class ClientConnectionInfo + { + 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 string DashboardId { get; set; } + public string UserAgent { get; set; } + public string IpAddress { get; set; } + public HashSet MonitoredDevices { get; set; } = new HashSet(); + public HashSet StreamingDevices { get; set; } = new HashSet(); + } + + public class DeviceStreamingInfo + { + public int DeviceId { get; set; } + public int IntervalMs { get; set; } + public DateTime StartedAt { get; set; } + public DateTime LastUpdate { get; set; } + public HashSet ClientsStreaming { get; set; } = new HashSet(); + } + + // These are the same models as in the RealTimeService but duplicated here for SignalR-specific usage + 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 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 DeviceSummaries { get; set; } + } + + public class TestResult + { + public string TestId { get; set; } + public DateTime Timestamp { get; set; } + public int ConnectedClients { get; set; } + public List ActiveStreamingDevices { get; set; } + public string Status { get; set; } + } + + #endregion +} \ No newline at end of file diff --git a/Haoliang.Api/Middleware/ExceptionMiddleware.cs b/Haoliang.Api/Middleware/ExceptionMiddleware.cs new file mode 100644 index 0000000..a0fa88b --- /dev/null +++ b/Haoliang.Api/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,136 @@ +using System; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Middleware +{ + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILoggingService _loggingService; + + public ExceptionMiddleware(RequestDelegate next, ILoggingService loggingService) + { + _next = next; + _loggingService = loggingService; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + + private async Task HandleExceptionAsync(HttpContext context, Exception exception) + { + context.Response.ContentType = "application/json"; + + var response = new ApiResponse + { + Timestamp = DateTime.Now, + Success = false + }; + + switch (exception) + { + case UnauthorizedAccessException _: + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + response.Message = "Unauthorized access"; + response.ErrorCode = 401; + await _loggingService.LogWarningAsync($"Unauthorized access: {exception.Message}"); + break; + + case ForbiddenException _: + context.Response.StatusCode = (int)HttpStatusCode.Forbidden; + response.Message = "Access forbidden"; + response.ErrorCode = 403; + await _loggingService.LogWarningAsync($"Access forbidden: {exception.Message}"); + break; + + case NotFoundException _: + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + response.Message = "Resource not found"; + response.ErrorCode = 404; + await _loggingService.LogWarningAsync($"Resource not found: {exception.Message}"); + break; + + case BadRequestException _: + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + response.Message = "Bad request"; + response.ErrorCode = 400; + await _loggingService.LogWarningAsync($"Bad request: {exception.Message}"); + break; + + case ValidationException _: + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + response.Message = "Validation failed"; + response.ErrorCode = 400; + response.Data = ((ValidationException)exception).Errors; + await _loggingService.LogWarningAsync($"Validation failed: {exception.Message}"); + break; + + case ConflictException _: + context.Response.StatusCode = (int)HttpStatusCode.Conflict; + response.Message = "Resource conflict"; + response.ErrorCode = 409; + await _loggingService.LogWarningAsync($"Resource conflict: {exception.Message}"); + break; + + default: + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + response.Message = "An unexpected error occurred"; + response.ErrorCode = 500; + response.Data = new { + Detail = exception.Message, + StackTrace = exception.StackTrace + }; + await _loggingService.LogErrorAsync($"Unhandled exception: {exception.Message}", exception); + break; + } + + var jsonResponse = JsonSerializer.Serialize(response); + await context.Response.WriteAsync(jsonResponse); + } + } + + // Custom exception classes + public class ForbiddenException : Exception + { + public ForbiddenException(string message) : base(message) { } + } + + public class NotFoundException : Exception + { + public NotFoundException(string message) : base(message) { } + } + + public class BadRequestException : Exception + { + public BadRequestException(string message) : base(message) { } + } + + public class ValidationException : Exception + { + public object Errors { get; set; } + + public ValidationException(string message, object errors = null) : base(message) + { + Errors = errors; + } + } + + public class ConflictException : Exception + { + public ConflictException(string message) : base(message) { } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Middleware/JwtMiddleware.cs b/Haoliang.Api/Middleware/JwtMiddleware.cs new file mode 100644 index 0000000..fff2efd --- /dev/null +++ b/Haoliang.Api/Middleware/JwtMiddleware.cs @@ -0,0 +1,83 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Middleware +{ + public class JwtMiddleware + { + private readonly RequestDelegate _next; + private readonly IAuthService _authService; + private readonly JwtSettings _jwtSettings; + + public JwtMiddleware(RequestDelegate next, IAuthService authService, IOptions jwtSettings) + { + _next = next; + _authService = authService; + _jwtSettings = jwtSettings.Value; + } + + public async Task Invoke(HttpContext context) + { + var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); + + if (token != null) + await AttachUserToContext(context, token); + + await _next(context); + } + + private async Task AttachUserToContext(HttpContext context, string token) + { + try + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret); + + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + ClockSkew = TimeSpan.Zero + }, out SecurityToken validatedToken); + + var jwtToken = (JwtSecurityToken)validatedToken; + var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); + + // Attach user to context on successful jwt validation + context.Items["User"] = await _authService.GetUserByIdAsync(userId); + } + catch (Exception ex) + { + // Token is not valid + // Log the error but don't throw, allow the request to continue + // The authorization filter will handle the authentication failure + } + } + } + + public class JwtSettings + { + public string Secret { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + public int ExpirationMinutes { get; set; } = 60; + } + + public static class JwtMiddlewareExtensions + { + public static IApplicationBuilder UseJwtMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Middleware/LoggingMiddleware.cs b/Haoliang.Api/Middleware/LoggingMiddleware.cs new file mode 100644 index 0000000..d766cc9 --- /dev/null +++ b/Haoliang.Api/Middleware/LoggingMiddleware.cs @@ -0,0 +1,162 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Haoliang.Core.Services; + +namespace Haoliang.Api.Middleware +{ + public class LoggingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly ILoggingService _loggingService; + + public LoggingMiddleware(RequestDelegate next, ILogger logger, ILoggingService loggingService) + { + _next = next; + _logger = logger; + _loggingService = loggingService; + } + + public async Task Invoke(HttpContext context) + { + var originalBodyStream = context.Response.Body; + + try + { + // Log request + await LogRequestAsync(context); + + // Capture response + using (var responseBody = new MemoryStream()) + { + context.Response.Body = responseBody; + + await _next(context); + + // Log response + await LogResponseAsync(context, responseBody); + + // Copy the response body to the original stream + responseBody.Seek(0, SeekOrigin.Begin); + await responseBody.CopyToAsync(originalBodyStream); + } + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Unhandled exception in logging middleware: {ex.Message}", ex); + throw; + } + } + + private async Task LogRequestAsync(HttpContext context) + { + try + { + var request = context.Request; + + // Don't log request body for sensitive endpoints like login + var shouldLogBody = !request.Path.ToString().Contains("/login") && + !request.Path.ToString().Contains("/auth"); + + var requestBody = shouldLogBody ? await GetRequestBodyAsync(request) : "[REDACTED]"; + + var logData = new + { + Method = request.Method, + Path = request.Path, + QueryString = request.QueryString.ToString(), + Headers = GetSanitizedHeaders(request.Headers), + Body = requestBody, + UserAgent = request.Headers["User-Agent"].ToString(), + RemoteIpAddress = context.Connection.RemoteIpAddress?.ToString(), + Timestamp = DateTime.Now + }; + + await _loggingService.LogInfoAsync($"Incoming request: {JsonSerializer.Serialize(logData)}"); + _logger.LogInformation("Incoming request: {Request}", JsonSerializer.Serialize(logData)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Error logging request: {ex.Message}", ex); + } + } + + private async Task LogResponseAsync(HttpContext context, MemoryStream responseBody) + { + try + { + var response = context.Response; + var responseBodyContent = await GetResponseBodyAsync(responseBody); + + var logData = new + { + StatusCode = response.StatusCode, + Headers = GetSanitizedHeaders(response.Headers), + Body = responseBodyContent, + Timestamp = DateTime.Now + }; + + await _loggingService.LogInfoAsync($"Outgoing response: {JsonSerializer.Serialize(logData)}"); + _logger.LogInformation("Outgoing response: {Response}", JsonSerializer.Serialize(logData)); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Error logging response: {ex.Message}", ex); + } + } + + private async Task GetRequestBodyAsync(HttpRequest request) + { + request.EnableBuffering(); + + using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true)) + { + var body = await reader.ReadToEndAsync(); + request.Body.Seek(0, SeekOrigin.Begin); + return body; + } + } + + private async Task GetResponseBodyAsync(MemoryStream responseBody) + { + responseBody.Seek(0, SeekOrigin.Begin); + using (var reader = new StreamReader(responseBody, Encoding.UTF8)) + { + var body = await reader.ReadToEndAsync(); + return body; + } + } + + private object GetSanitizedHeaders(IHeaderDictionary headers) + { + var sensitiveHeaders = new[] { "authorization", "cookie", "set-cookie", "password", "token" }; + var sanitizedHeaders = new Dictionary(); + + foreach (var header in headers) + { + if (sensitiveHeaders.Contains(header.Key.ToLower())) + { + sanitizedHeaders[header.Key] = "[REDACTED]"; + } + else + { + sanitizedHeaders[header.Key] = header.Value.ToString(); + } + } + + return sanitizedHeaders; + } + } + + public static class LoggingMiddlewareExtensions + { + public static IApplicationBuilder UseLoggingMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Middleware/RateLimitMiddleware.cs b/Haoliang.Api/Middleware/RateLimitMiddleware.cs new file mode 100644 index 0000000..cd9c62d --- /dev/null +++ b/Haoliang.Api/Middleware/RateLimitMiddleware.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Haoliang.Core.Services; +using Haoliang.Models.Common; + +namespace Haoliang.Api.Middleware +{ + public class RateLimitMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly ILoggingService _loggingService; + private readonly RateLimitSettings _settings; + private readonly ConcurrentDictionary _requestTimestamps = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _requestCounts = new ConcurrentDictionary(); + + public RateLimitMiddleware( + RequestDelegate next, + ILogger logger, + ILoggingService loggingService, + IOptions settings) + { + _next = next; + _logger = logger; + _loggingService = loggingService; + _settings = settings.Value; + } + + public async Task Invoke(HttpContext context) + { + var clientId = GetClientId(context); + var endpoint = GetEndpoint(context); + + if (IsRateLimited(clientId, endpoint)) + { + await LogRateLimitExceeded(clientId, endpoint); + context.Response.StatusCode = 429; + context.Response.Headers["X-RateLimit-Limit"] = _settings.MaxRequests.ToString(); + context.Response.Headers["X-RateLimit-Remaining"] = "0"; + context.Response.Headers["X-RateLimit-Reset"] = GetResetTime().ToString(); + + var response = new ApiResponse + { + Success = false, + Message = "Rate limit exceeded. Please try again later.", + ErrorCode = 429, + Timestamp = DateTime.Now + }; + + await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(response)); + return; + } + + await _next(context); + + // Update rate limit headers + context.Response.Headers["X-RateLimit-Limit"] = _settings.MaxRequests.ToString(); + context.Response.Headers["X-RateLimit-Remaining"] = GetRemainingRequests(clientId, endpoint).ToString(); + context.Response.Headers["X-RateLimit-Reset"] = GetResetTime().ToString(); + } + + private bool IsRateLimited(string clientId, string endpoint) + { + var key = $"{clientId}:{endpoint}"; + var now = DateTime.UtcNow; + var windowStart = now.AddSeconds(-_settings.TimeWindow); + + // Clean old entries + CleanupOldEntries(windowStart); + + // Check if we need to reset the count + if (_requestCounts.TryGetValue(key, out var count)) + { + if (count >= _settings.MaxRequests) + { + // Check if the time window has reset + if (_requestTimestamps.TryGetValue(key, out var timestamp) && timestamp < windowStart) + { + _requestCounts[key] = 1; + _requestTimestamps[key] = now; + return false; + } + return true; + } + } + + // Increment count + _requestCounts.AddOrUpdate(key, 1, (_, _) => count + 1); + _requestTimestamps.AddOrUpdate(key, now, (_, _) => now); + + return false; + } + + private void CleanupOldEntries(DateTime windowStart) + { + var oldKeys = _requestTimestamps + .Where(kvp => kvp.Value < windowStart) + .Select(kvp => kvp.Key) + .ToList(); + + foreach (var key in oldKeys) + { + _requestTimestamps.TryRemove(key, out _); + _requestCounts.TryRemove(key, out _); + } + } + + private int GetRemainingRequests(string clientId, string endpoint) + { + var key = $"{clientId}:{endpoint}"; + var maxRequests = _settings.MaxRequests; + var currentCount = _requestCounts.TryGetValue(key, out var count) ? count : 0; + return Math.Max(0, maxRequests - currentCount); + } + + private DateTime GetResetTime() + { + return DateTime.UtcNow.AddSeconds(_settings.TimeWindow); + } + + private async Task LogRateLimitExceeded(string clientId, string endpoint) + { + var logData = new + { + ClientId = clientId, + Endpoint = endpoint, + Timestamp = DateTime.Now, + MaxRequests = _settings.MaxRequests, + TimeWindow = _settings.TimeWindow + }; + + await _loggingService.LogWarningAsync($"Rate limit exceeded: {JsonSerializer.Serialize(logData)}"); + _logger.LogWarning("Rate limit exceeded for client {ClientId} on endpoint {Endpoint}", clientId, endpoint); + } + + private string GetClientId(HttpContext context) + { + // Try to get client ID from various sources + if (context.User?.Identity?.IsAuthenticated == true) + { + return context.User.Identity.Name ?? "authenticated"; + } + + // Use IP address as fallback + return context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; + } + + private string GetEndpoint(HttpContext context) + { + return context.Request.Path.ToString(); + } + } + + public class RateLimitSettings + { + public int MaxRequests { get; set; } = 100; + public int TimeWindow { get; set; } = 60; // in seconds + public bool EnableRateLimiting { get; set; } = true; + public bool ExcludeHealthChecks { get; set; } = true; + public string[] ExcludedPaths { get; set; } = Array.Empty(); + } + + public static class RateLimitMiddlewareExtensions + { + public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder, Action configure = null) + { + var settings = new RateLimitSettings(); + configure?.Invoke(settings); + + return builder.UseMiddleware(Options.Create(settings)); + } + } +} \ No newline at end of file diff --git a/Haoliang.Api/Startup.cs b/Haoliang.Api/Startup.cs index 1f9a68a..820df8c 100644 --- a/Haoliang.Api/Startup.cs +++ b/Haoliang.Api/Startup.cs @@ -84,6 +84,16 @@ namespace Haoliang.Api services.AddDbContext(options => options.UseSqlServer(connectionString)); + // 配置内存缓存 + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + + // 注册缓存服务 + services.AddSingleton(); + + // 注册状态机服务 + services.AddHostedService(); + // 注册服务 services.AddScoped(); services.AddScoped(); @@ -93,15 +103,21 @@ namespace Haoliang.Api services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // 注册配置服务 + services.AddScoped(); // 注册仓储 services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Haoliang.Api/obj/Debug/net6.0/Haoliang.Api.assets.cache b/Haoliang.Api/obj/Debug/net6.0/Haoliang.Api.assets.cache index fbcbffe..2c101fe 100644 Binary files a/Haoliang.Api/obj/Debug/net6.0/Haoliang.Api.assets.cache and b/Haoliang.Api/obj/Debug/net6.0/Haoliang.Api.assets.cache differ diff --git a/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.dgspec.json b/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.dgspec.json index 0c3bcaf..6b3adca 100644 --- a/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.dgspec.json +++ b/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.dgspec.json @@ -50,27 +50,27 @@ "dependencies": { "Microsoft.AspNetCore.Cors": { "target": "Package", - "version": "[2.2.48, )" + "version": "[2.3.0, )" }, "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { "target": "Package", - "version": "[6.0.32, )" + "version": "[6.0.0, )" }, "Microsoft.EntityFrameworkCore.Design": { "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", "suppressParent": "All", "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.2, )" }, "Microsoft.EntityFrameworkCore.Tools": { "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", "suppressParent": "All", "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.2, )" }, "Pomelo.EntityFrameworkCore.MySql": { "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.0, )" }, "Swashbuckle.AspNetCore": { "target": "Package", @@ -196,9 +196,21 @@ "net6.0": { "targetAlias": "net6.0", "dependencies": { + "Microsoft.EntityFrameworkCore": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Design": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Tools": { + "target": "Package", + "version": "[7.0.2, )" + }, "Pomelo.EntityFrameworkCore.MySql": { "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.0, )" } }, "imports": [ diff --git a/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.props b/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.props index a66975b..05a849d 100644 --- a/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.props +++ b/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.props @@ -16,10 +16,10 @@ - + /root/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5 - /root/.nuget/packages/microsoft.entityframeworkcore.tools/6.0.32 + /root/.nuget/packages/microsoft.entityframeworkcore.tools/7.0.2 \ No newline at end of file diff --git a/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.targets b/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.targets index 198d2e0..7d031bf 100644 --- a/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.targets +++ b/Haoliang.Api/obj/Haoliang.Api.csproj.nuget.g.targets @@ -1,6 +1,7 @@  + diff --git a/Haoliang.Api/obj/project.assets.json b/Haoliang.Api/obj/project.assets.json index 79e69b2..9726e89 100644 --- a/Haoliang.Api/obj/project.assets.json +++ b/Haoliang.Api/obj/project.assets.json @@ -2,13 +2,13 @@ "version": 3, "targets": { "net6.0": { - "Humanizer.Core/2.8.26": { + "Humanizer.Core/2.14.1": { "type": "package", "compile": { - "lib/netstandard2.0/_._": {} + "lib/net6.0/Humanizer.dll": {} }, "runtime": { - "lib/netstandard2.0/Humanizer.dll": {} + "lib/net6.0/Humanizer.dll": {} } }, "Microsoft.AspNetCore.Cors/2.3.0": { @@ -67,7 +67,7 @@ "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {} } }, - "Microsoft.AspNetCore.JsonPatch/6.0.32": { + "Microsoft.AspNetCore.JsonPatch/6.0.0": { "type": "package", "dependencies": { "Microsoft.CSharp": "4.7.0", @@ -80,10 +80,10 @@ "lib/net6.0/Microsoft.AspNetCore.JsonPatch.dll": {} } }, - "Microsoft.AspNetCore.Mvc.NewtonsoftJson/6.0.32": { + "Microsoft.AspNetCore.Mvc.NewtonsoftJson/6.0.0": { "type": "package", "dependencies": { - "Microsoft.AspNetCore.JsonPatch": "6.0.32", + "Microsoft.AspNetCore.JsonPatch": "6.0.0", "Newtonsoft.Json": "13.0.1", "Newtonsoft.Json.Bson": "1.0.2" }, @@ -143,11 +143,13 @@ "lib/netstandard2.0/_._": {} } }, - "Microsoft.EntityFrameworkCore.Design/6.0.32": { + "Microsoft.EntityFrameworkCore.Design/7.0.2": { "type": "package", "dependencies": { - "Humanizer.Core": "2.8.26", - "Microsoft.EntityFrameworkCore.Relational": "6.0.32" + "Humanizer.Core": "2.14.1", + "Microsoft.EntityFrameworkCore.Relational": "7.0.2", + "Microsoft.Extensions.DependencyModel": "7.0.0", + "Mono.TextTemplating": "2.2.1" }, "compile": { "lib/net6.0/_._": {} @@ -172,10 +174,10 @@ "lib/net6.0/Microsoft.EntityFrameworkCore.Relational.dll": {} } }, - "Microsoft.EntityFrameworkCore.Tools/6.0.32": { + "Microsoft.EntityFrameworkCore.Tools/7.0.2": { "type": "package", "dependencies": { - "Microsoft.EntityFrameworkCore.Design": "6.0.32" + "Microsoft.EntityFrameworkCore.Design": "7.0.2" }, "compile": { "lib/net6.0/_._": {} @@ -271,6 +273,22 @@ "buildTransitive/net6.0/_._": {} } }, + "Microsoft.Extensions.DependencyModel/7.0.0": { + "type": "package", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0", + "System.Text.Json": "7.0.0" + }, + "compile": { + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, "Microsoft.Extensions.FileProviders.Abstractions/8.0.0": { "type": "package", "dependencies": { @@ -373,6 +391,18 @@ "lib/netstandard2.0/Microsoft.OpenApi.dll": {} } }, + "Mono.TextTemplating/2.2.1": { + "type": "package", + "dependencies": { + "System.CodeDom": "4.4.0" + }, + "compile": { + "lib/netstandard2.0/Mono.TextTemplating.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Mono.TextTemplating.dll": {} + } + }, "MySqlConnector/2.2.5": { "type": "package", "compile": { @@ -476,6 +506,15 @@ "lib/netcoreapp2.1/_._": {} } }, + "System.CodeDom/4.4.0": { + "type": "package", + "compile": { + "ref/netstandard2.0/System.CodeDom.dll": {} + }, + "runtime": { + "lib/netstandard2.0/System.CodeDom.dll": {} + } + }, "System.Diagnostics.DiagnosticSource/8.0.1": { "type": "package", "dependencies": { @@ -524,6 +563,22 @@ } } }, + "System.Text.Json/7.0.0": { + "type": "package", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "7.0.0" + }, + "compile": { + "lib/net6.0/System.Text.Json.dll": {} + }, + "runtime": { + "lib/net6.0/System.Text.Json.dll": {} + }, + "build": { + "buildTransitive/net6.0/System.Text.Json.targets": {} + } + }, "Haoliang.Core/1.0.0": { "type": "project", "framework": ".NETCoreApp,Version=v6.0", @@ -543,7 +598,10 @@ "dependencies": { "Haoliang.Core": "1.0.0", "Haoliang.Models": "1.0.0", - "Pomelo.EntityFrameworkCore.MySql": "6.0.32" + "Microsoft.EntityFrameworkCore": "7.0.2", + "Microsoft.EntityFrameworkCore.Design": "7.0.2", + "Microsoft.EntityFrameworkCore.Tools": "7.0.2", + "Pomelo.EntityFrameworkCore.MySql": "7.0.0" }, "compile": { "bin/placeholder/Haoliang.Data.dll": {} @@ -565,15 +623,17 @@ } }, "libraries": { - "Humanizer.Core/2.8.26": { - "sha512": "OiKusGL20vby4uDEswj2IgkdchC1yQ6rwbIkZDVBPIR6al2b7n3pC91elBul9q33KaBgRKhbZH3+2Ur4fnWx2A==", + "Humanizer.Core/2.14.1": { + "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", "type": "package", - "path": "humanizer.core/2.8.26", + "path": "humanizer.core/2.14.1", "files": [ ".nupkg.metadata", ".signature.p7s", - "humanizer.core.2.8.26.nupkg.sha512", + "humanizer.core.2.14.1.nupkg.sha512", "humanizer.core.nuspec", + "lib/net6.0/Humanizer.dll", + "lib/net6.0/Humanizer.xml", "lib/netstandard1.0/Humanizer.dll", "lib/netstandard1.0/Humanizer.xml", "lib/netstandard2.0/Humanizer.dll", @@ -633,10 +693,10 @@ "microsoft.aspnetcore.http.features.nuspec" ] }, - "Microsoft.AspNetCore.JsonPatch/6.0.32": { - "sha512": "ws85ncfMJYYe2MhiThXGqguu91u7N/qDUvJTVCD4nYxDXgtpE1xLt+yp9Qe5D5ayeExE4MLq3uMYX4ITbAjauQ==", + "Microsoft.AspNetCore.JsonPatch/6.0.0": { + "sha512": "SUiwg0XQ5NtmnELHXSdX4mAwawFnAOwSx2Zz6NIhQnEN1tZDoAWEHc8dS/S7y8cE52+9bHj+XbYZuLGF7OrQPA==", "type": "package", - "path": "microsoft.aspnetcore.jsonpatch/6.0.32", + "path": "microsoft.aspnetcore.jsonpatch/6.0.0", "files": [ ".nupkg.metadata", ".signature.p7s", @@ -648,14 +708,14 @@ "lib/net6.0/Microsoft.AspNetCore.JsonPatch.xml", "lib/netstandard2.0/Microsoft.AspNetCore.JsonPatch.dll", "lib/netstandard2.0/Microsoft.AspNetCore.JsonPatch.xml", - "microsoft.aspnetcore.jsonpatch.6.0.32.nupkg.sha512", + "microsoft.aspnetcore.jsonpatch.6.0.0.nupkg.sha512", "microsoft.aspnetcore.jsonpatch.nuspec" ] }, - "Microsoft.AspNetCore.Mvc.NewtonsoftJson/6.0.32": { - "sha512": "jRsEMFadT2Mtwnpadj7KB8pe7CiaR+TWEMOsCuJfUu+i2Puu4Y42XrIy8zkLpvrOz/XGEs5/i0FFztLKiNDvnw==", + "Microsoft.AspNetCore.Mvc.NewtonsoftJson/6.0.0": { + "sha512": "YMwSWgBuwkVn9k4/2wWxfwEbx8T5Utj13UH/zmUm5lbkKcY+tJyt9w9P4rY5pO1XtCitoh1+L+Feqz9qxG/CvA==", "type": "package", - "path": "microsoft.aspnetcore.mvc.newtonsoftjson/6.0.32", + "path": "microsoft.aspnetcore.mvc.newtonsoftjson/6.0.0", "files": [ ".nupkg.metadata", ".signature.p7s", @@ -663,7 +723,7 @@ "THIRD-PARTY-NOTICES.TXT", "lib/net6.0/Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll", "lib/net6.0/Microsoft.AspNetCore.Mvc.NewtonsoftJson.xml", - "microsoft.aspnetcore.mvc.newtonsoftjson.6.0.32.nupkg.sha512", + "microsoft.aspnetcore.mvc.newtonsoftjson.6.0.0.nupkg.sha512", "microsoft.aspnetcore.mvc.newtonsoftjson.nuspec" ] }, @@ -779,10 +839,10 @@ "microsoft.entityframeworkcore.analyzers.nuspec" ] }, - "Microsoft.EntityFrameworkCore.Design/6.0.32": { - "sha512": "rzccUMersJKA/+fqoG6bJrMLW77uJYENddYl+0DlfgPl48y+6XAMWhlfcoPPkaDMTqEKCS5QxNbijDagruNQmQ==", + "Microsoft.EntityFrameworkCore.Design/7.0.2": { + "sha512": "gUUucCoJci8BBxOcU/XuuldUZpCo5Od8lwFEzZ5WywnvDfSmARnXNe97BpjL+JiBhSrnuTxW/wCJjWqPonXXHQ==", "type": "package", - "path": "microsoft.entityframeworkcore.design/6.0.32", + "path": "microsoft.entityframeworkcore.design/7.0.2", "files": [ ".nupkg.metadata", ".signature.p7s", @@ -790,7 +850,7 @@ "build/net6.0/Microsoft.EntityFrameworkCore.Design.props", "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll", "lib/net6.0/Microsoft.EntityFrameworkCore.Design.xml", - "microsoft.entityframeworkcore.design.6.0.32.nupkg.sha512", + "microsoft.entityframeworkcore.design.7.0.2.nupkg.sha512", "microsoft.entityframeworkcore.design.nuspec" ] }, @@ -808,17 +868,17 @@ "microsoft.entityframeworkcore.relational.nuspec" ] }, - "Microsoft.EntityFrameworkCore.Tools/6.0.32": { - "sha512": "sXOfcLzaZI1gBC6AVDz+XUUt+Hoh42spdESHMXlq7Zo9sZcffkma16aKomBop5ZI+18g1ghQ1Mufqjj4iiMIuA==", + "Microsoft.EntityFrameworkCore.Tools/7.0.2": { + "sha512": "0Jx9feeGsUUlI+PEFkADyfQrGU+UIYh9N1I8ZO6X5bjYSKL2V1empkGTupvfrI7S9h4uA7aY8GQpjkCmIep7dg==", "type": "package", - "path": "microsoft.entityframeworkcore.tools/6.0.32", + "path": "microsoft.entityframeworkcore.tools/7.0.2", "hasTools": true, "files": [ ".nupkg.metadata", ".signature.p7s", "Icon.png", "lib/net6.0/_._", - "microsoft.entityframeworkcore.tools.6.0.32.nupkg.sha512", + "microsoft.entityframeworkcore.tools.7.0.2.nupkg.sha512", "microsoft.entityframeworkcore.tools.nuspec", "tools/EntityFrameworkCore.PS2.psd1", "tools/EntityFrameworkCore.PS2.psm1", @@ -1209,6 +1269,34 @@ "useSharedDesignerContext.txt" ] }, + "Microsoft.Extensions.DependencyModel/7.0.0": { + "sha512": "oONNYd71J3LzkWc4fUHl3SvMfiQMYUCo/mDHDEu76hYYxdhdrPYv6fvGv9nnKVyhE9P0h20AU8RZB5OOWQcAXg==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net6.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net7.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net7.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.7.0.0.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, "Microsoft.Extensions.FileProviders.Abstractions/8.0.0": { "sha512": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", "type": "package", @@ -1446,6 +1534,19 @@ "microsoft.openapi.nuspec" ] }, + "Mono.TextTemplating/2.2.1": { + "sha512": "KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==", + "type": "package", + "path": "mono.texttemplating/2.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net472/Mono.TextTemplating.dll", + "lib/netstandard2.0/Mono.TextTemplating.dll", + "mono.texttemplating.2.2.1.nupkg.sha512", + "mono.texttemplating.nuspec" + ] + }, "MySqlConnector/2.2.5": { "sha512": "6sinY78RvryhHwpup3awdjYO7d5hhWahb5p/1VDODJhSxJggV/sBbYuKK5IQF9TuzXABiddqUbmRfM884tqA3Q==", "type": "package", @@ -1648,6 +1749,27 @@ "system.buffers.nuspec" ] }, + "System.CodeDom/4.4.0": { + "sha512": "2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA==", + "type": "package", + "path": "system.codedom/4.4.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.dll", + "ref/net461/System.CodeDom.dll", + "ref/net461/System.CodeDom.xml", + "ref/netstandard2.0/System.CodeDom.dll", + "ref/netstandard2.0/System.CodeDom.xml", + "system.codedom.4.4.0.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, "System.Diagnostics.DiagnosticSource/8.0.1": { "sha512": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==", "type": "package", @@ -1737,6 +1859,77 @@ "useSharedDesignerContext.txt" ] }, + "System.Text.Json/7.0.0": { + "sha512": "DaGSsVqKsn/ia6RG8frjwmJonfos0srquhw09TlT8KRw5I43E+4gs+/bZj4K0vShJ5H9imCuXupb4RmS+dBy3w==", + "type": "package", + "path": "system.text.json/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "buildTransitive/net461/System.Text.Json.targets", + "buildTransitive/net462/System.Text.Json.targets", + "buildTransitive/net6.0/System.Text.Json.targets", + "buildTransitive/netcoreapp2.0/System.Text.Json.targets", + "buildTransitive/netstandard2.0/System.Text.Json.targets", + "lib/net462/System.Text.Json.dll", + "lib/net462/System.Text.Json.xml", + "lib/net6.0/System.Text.Json.dll", + "lib/net6.0/System.Text.Json.xml", + "lib/net7.0/System.Text.Json.dll", + "lib/net7.0/System.Text.Json.xml", + "lib/netstandard2.0/System.Text.Json.dll", + "lib/netstandard2.0/System.Text.Json.xml", + "system.text.json.7.0.0.nupkg.sha512", + "system.text.json.nuspec", + "useSharedDesignerContext.txt" + ] + }, "Haoliang.Core/1.0.0": { "type": "project", "path": "../Haoliang.Core/Haoliang.Core.csproj", @@ -1758,11 +1951,11 @@ "Haoliang.Core >= 1.0.0", "Haoliang.Data >= 1.0.0", "Haoliang.Models >= 1.0.0", - "Microsoft.AspNetCore.Cors >= 2.2.48", - "Microsoft.AspNetCore.Mvc.NewtonsoftJson >= 6.0.32", - "Microsoft.EntityFrameworkCore.Design >= 6.0.32", - "Microsoft.EntityFrameworkCore.Tools >= 6.0.32", - "Pomelo.EntityFrameworkCore.MySql >= 6.0.32", + "Microsoft.AspNetCore.Cors >= 2.3.0", + "Microsoft.AspNetCore.Mvc.NewtonsoftJson >= 6.0.0", + "Microsoft.EntityFrameworkCore.Design >= 7.0.2", + "Microsoft.EntityFrameworkCore.Tools >= 7.0.2", + "Pomelo.EntityFrameworkCore.MySql >= 7.0.0", "Swashbuckle.AspNetCore >= 6.5.0" ] }, @@ -1815,27 +2008,27 @@ "dependencies": { "Microsoft.AspNetCore.Cors": { "target": "Package", - "version": "[2.2.48, )" + "version": "[2.3.0, )" }, "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { "target": "Package", - "version": "[6.0.32, )" + "version": "[6.0.0, )" }, "Microsoft.EntityFrameworkCore.Design": { "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", "suppressParent": "All", "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.2, )" }, "Microsoft.EntityFrameworkCore.Tools": { "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", "suppressParent": "All", "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.2, )" }, "Pomelo.EntityFrameworkCore.MySql": { "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.0, )" }, "Swashbuckle.AspNetCore": { "target": "Package", @@ -1863,27 +2056,5 @@ "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/6.0.136/RuntimeIdentifierGraph.json" } } - }, - "logs": [ - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Api depends on Microsoft.AspNetCore.Cors (>= 2.2.48) but Microsoft.AspNetCore.Cors 2.2.48 was not found. An approximate best match of Microsoft.AspNetCore.Cors 2.3.0 was resolved.", - "libraryId": "Microsoft.AspNetCore.Cors", - "targetGraphs": [ - "net6.0" - ] - }, - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Api depends on Pomelo.EntityFrameworkCore.MySql (>= 6.0.32) but Pomelo.EntityFrameworkCore.MySql 6.0.32 was not found. An approximate best match of Pomelo.EntityFrameworkCore.MySql 7.0.0 was resolved.", - "libraryId": "Pomelo.EntityFrameworkCore.MySql", - "targetGraphs": [ - "net6.0" - ] - } - ] + } } \ No newline at end of file diff --git a/Haoliang.Api/obj/project.nuget.cache b/Haoliang.Api/obj/project.nuget.cache index 422baf2..9560803 100644 --- a/Haoliang.Api/obj/project.nuget.cache +++ b/Haoliang.Api/obj/project.nuget.cache @@ -1,29 +1,30 @@ { "version": 2, - "dgSpecHash": "YEg7RxHHPJcvb4arg9K7Hd2iU2WbzKoXnxc2vE3FptqJ9uF58pCG8r+51bK+gsZuBi4MKQnQXabHxjNWxZIpxQ==", + "dgSpecHash": "rf8aFVRiFQwg0xDg31tFCBl3lY9bGLl2JY9FWoCZLzcRy7s21dFiCjsegb24H1j4sj1KmlSdHAfbur13AerStw==", "success": true, "projectFilePath": "/root/opencode/haoliang/Haoliang.Api/Haoliang.Api.csproj", "expectedPackageFiles": [ - "/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.aspnetcore.cors/2.3.0/microsoft.aspnetcore.cors.2.3.0.nupkg.sha512", "/root/.nuget/packages/microsoft.aspnetcore.http.abstractions/2.3.0/microsoft.aspnetcore.http.abstractions.2.3.0.nupkg.sha512", "/root/.nuget/packages/microsoft.aspnetcore.http.extensions/2.3.0/microsoft.aspnetcore.http.extensions.2.3.0.nupkg.sha512", "/root/.nuget/packages/microsoft.aspnetcore.http.features/2.3.0/microsoft.aspnetcore.http.features.2.3.0.nupkg.sha512", - "/root/.nuget/packages/microsoft.aspnetcore.jsonpatch/6.0.32/microsoft.aspnetcore.jsonpatch.6.0.32.nupkg.sha512", - "/root/.nuget/packages/microsoft.aspnetcore.mvc.newtonsoftjson/6.0.32/microsoft.aspnetcore.mvc.newtonsoftjson.6.0.32.nupkg.sha512", + "/root/.nuget/packages/microsoft.aspnetcore.jsonpatch/6.0.0/microsoft.aspnetcore.jsonpatch.6.0.0.nupkg.sha512", + "/root/.nuget/packages/microsoft.aspnetcore.mvc.newtonsoftjson/6.0.0/microsoft.aspnetcore.mvc.newtonsoftjson.6.0.0.nupkg.sha512", "/root/.nuget/packages/microsoft.csharp/4.7.0/microsoft.csharp.4.7.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/6.0.32/microsoft.entityframeworkcore.design.6.0.32.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/6.0.32/microsoft.entityframeworkcore.tools.6.0.32.nupkg.sha512", + "/root/.nuget/packages/microsoft.entityframeworkcore.tools/7.0.2/microsoft.entityframeworkcore.tools.7.0.2.nupkg.sha512", "/root/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.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/8.0.0/microsoft.extensions.configuration.abstractions.8.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/8.0.2/microsoft.extensions.dependencyinjection.abstractions.8.0.2.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/8.0.0/microsoft.extensions.fileproviders.abstractions.8.0.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/8.0.2/microsoft.extensions.logging.abstractions.8.0.2.nupkg.sha512", @@ -31,6 +32,7 @@ "/root/.nuget/packages/microsoft.extensions.primitives/8.0.0/microsoft.extensions.primitives.8.0.0.nupkg.sha512", "/root/.nuget/packages/microsoft.net.http.headers/2.3.0/microsoft.net.http.headers.2.3.0.nupkg.sha512", "/root/.nuget/packages/microsoft.openapi/1.2.3/microsoft.openapi.1.2.3.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/13.0.1/newtonsoft.json.13.0.1.nupkg.sha512", "/root/.nuget/packages/newtonsoft.json.bson/1.0.2/newtonsoft.json.bson.1.0.2.nupkg.sha512", @@ -40,30 +42,11 @@ "/root/.nuget/packages/swashbuckle.aspnetcore.swaggergen/6.5.0/swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512", "/root/.nuget/packages/swashbuckle.aspnetcore.swaggerui/6.5.0/swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512", "/root/.nuget/packages/system.buffers/4.6.0/system.buffers.4.6.0.nupkg.sha512", + "/root/.nuget/packages/system.codedom/4.4.0/system.codedom.4.4.0.nupkg.sha512", "/root/.nuget/packages/system.diagnostics.diagnosticsource/8.0.1/system.diagnostics.diagnosticsource.8.0.1.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.text.encodings.web/8.0.0/system.text.encodings.web.8.0.0.nupkg.sha512" + "/root/.nuget/packages/system.text.encodings.web/8.0.0/system.text.encodings.web.8.0.0.nupkg.sha512", + "/root/.nuget/packages/system.text.json/7.0.0/system.text.json.7.0.0.nupkg.sha512" ], - "logs": [ - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Api depends on Microsoft.AspNetCore.Cors (>= 2.2.48) but Microsoft.AspNetCore.Cors 2.2.48 was not found. An approximate best match of Microsoft.AspNetCore.Cors 2.3.0 was resolved.", - "libraryId": "Microsoft.AspNetCore.Cors", - "targetGraphs": [ - "net6.0" - ] - }, - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Api depends on Pomelo.EntityFrameworkCore.MySql (>= 6.0.32) but Pomelo.EntityFrameworkCore.MySql 6.0.32 was not found. An approximate best match of Pomelo.EntityFrameworkCore.MySql 7.0.0 was resolved.", - "libraryId": "Pomelo.EntityFrameworkCore.MySql", - "targetGraphs": [ - "net6.0" - ] - } - ] + "logs": [] } \ No newline at end of file diff --git a/Haoliang.Core/Haoliang.Core.csproj b/Haoliang.Core/Haoliang.Core.csproj index 98877e8..83791d4 100644 --- a/Haoliang.Core/Haoliang.Core.csproj +++ b/Haoliang.Core/Haoliang.Core.csproj @@ -2,6 +2,7 @@ + @@ -10,4 +11,14 @@ enable + + + + + + + + + + diff --git a/Haoliang.Core/Services/AlarmNotificationService.cs b/Haoliang.Core/Services/AlarmNotificationService.cs new file mode 100644 index 0000000..5e0384e --- /dev/null +++ b/Haoliang.Core/Services/AlarmNotificationService.cs @@ -0,0 +1,552 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Haoliang.Models.System; +using Haoliang.Models.Device; +using Haoliang.Data.Repositories; + +namespace Haoliang.Core.Services +{ + public interface IAlarmNotificationService + { + Task SendAlarmNotificationAsync(Alarm alarm); + Task SendBulkAlarmNotificationsAsync(IEnumerable alarms); + Task SendSmsNotificationAsync(string phoneNumber, string message); + Task SendEmailNotificationAsync(string email, string subject, string message); + Task SendWechatNotificationAsync(string openId, string message); + Task> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate); + Task ConfigureNotificationChannelAsync(NotificationChannel channel); + Task> GetAvailableChannelsAsync(); + Task TestNotificationChannelAsync(NotificationChannel channel); + Task GetFailedNotificationCountAsync(DateTime date); + Task ResendFailedNotificationsAsync(int maxRetries = 3); + } + + public class AlarmNotificationService : IAlarmNotificationService + { + private readonly IAlarmNotificationRepository _notificationRepository; + private readonly ISystemConfigRepository _configRepository; + private readonly ILoggingService _loggingService; + private readonly INotificationSender _smsSender; + private readonly INotificationSender _emailSender; + private readonly INotificationSender _wechatSender; + private readonly ConcurrentDictionary _channels; + + public AlarmNotificationService( + IAlarmNotificationRepository notificationRepository, + ISystemConfigRepository configRepository, + ILoggingService loggingService, + INotificationSender smsSender, + INotificationSender emailSender, + INotificationSender wechatSender) + { + _notificationRepository = notificationRepository; + _configRepository = configRepository; + _loggingService = loggingService; + _smsSender = smsSender; + _emailSender = emailSender; + _wechatSender = wechatSender; + _channels = new ConcurrentDictionary(); + } + + public async Task SendAlarmNotificationAsync(Alarm alarm) + { + if (alarm == null) + throw new ArgumentNullException(nameof(alarm)); + + if (!alarm.IsActive) + { + await _loggingService.LogDebugAsync($"Skipping notification for inactive alarm {alarm.AlarmId}"); + return; + } + + // Get notification channels for this alarm + var channels = await GetNotificationChannelsForAlarm(alarm); + + foreach (var channel in channels) + { + try + { + await SendNotificationViaChannelAsync(alarm, channel); + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Failed to send alarm {alarm.AlarmId} via {channel.ChannelType}: {ex.Message}", ex); + } + } + } + + public async Task SendBulkAlarmNotificationsAsync(IEnumerable alarms) + { + if (alarms == null) + throw new ArgumentNullException(nameof(alarms)); + + var alarmList = alarms.ToList(); + await _loggingService.LogInformationAsync($"Processing bulk notifications for {alarmList.Count} alarms"); + + var notificationTasks = alarmList.Select(alarm => SendAlarmNotificationAsync(alarm)); + await Task.WhenAll(notificationTasks); + + await _loggingService.LogInformationAsync($"Completed bulk notifications for {alarmList.Count} alarms"); + } + + public async Task SendSmsNotificationAsync(string phoneNumber, string message) + { + try + { + var smsConfig = await GetSmsConfigurationAsync(); + if (!smsConfig.IsEnabled) + { + await _loggingService.LogWarningAsync("SMS notifications are disabled"); + return false; + } + + var result = await _smsSender.SendAsync(phoneNumber, message, smsConfig); + + if (result.Success) + { + await _loggingService.LogInformationAsync($"SMS sent successfully to {phoneNumber}"); + } + else + { + await _loggingService.LogErrorAsync($"SMS failed to {phoneNumber}: {result.ErrorMessage}"); + } + + return result.Success; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"SMS notification error: {ex.Message}", ex); + return false; + } + } + + public async Task SendEmailNotificationAsync(string email, string subject, string message) + { + try + { + var emailConfig = await GetEmailConfigurationAsync(); + if (!emailConfig.IsEnabled) + { + await _loggingService.LogWarningAsync("Email notifications are disabled"); + return false; + } + + var result = await _emailSender.SendAsync(email, subject, message, emailConfig); + + if (result.Success) + { + await _loggingService.LogInformationAsync($"Email sent successfully to {email}"); + } + else + { + await _loggingService.LogErrorAsync($"Email failed to {email}: {result.ErrorMessage}"); + } + + return result.Success; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Email notification error: {ex.Message}", ex); + return false; + } + } + + public async Task SendWechatNotificationAsync(string openId, string message) + { + try + { + var wechatConfig = await GetWechatConfigurationAsync(); + if (!wechatConfig.IsEnabled) + { + await _loggingService.LogWarningAsync("WeChat notifications are disabled"); + return false; + } + + var result = await _wechatSender.SendAsync(openId, message, wechatConfig); + + if (result.Success) + { + await _loggingService.LogInformationAsync($"WeChat message sent successfully to {openId}"); + } + else + { + await _loggingService.LogErrorAsync($"WeChat message failed to {openId}: {result.ErrorMessage}"); + } + + return result.Success; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"WeChat notification error: {ex.Message}", ex); + return false; + } + } + + public async Task> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate) + { + return await _notificationRepository.GetNotificationsByDateRangeAsync(startDate, endDate); + } + + public async Task ConfigureNotificationChannelAsync(NotificationChannel channel) + { + if (channel == null) + throw new ArgumentNullException(nameof(channel)); + + // Validate channel configuration + if (!await ValidateChannelConfigurationAsync(channel)) + return false; + + // Save channel configuration + await _configRepository.UpsertAsync(new SystemConfig + { + ConfigKey = $"notification_{channel.ChannelType}_enabled", + ConfigValue = channel.IsEnabled.ToString(), + Category = "Notification", + Description = $"Enable {channel.ChannelType} notifications" + }); + + await _configRepository.UpsertAsync(new SystemConfig + { + ConfigKey = $"notification_{channel.ChannelType}_config", + ConfigValue = System.Text.Json.JsonSerializer.Serialize(channel.Settings), + Category = "Notification", + Description = $"{channel.ChannelType} notification settings" + }); + + _channels[channel.ChannelType] = channel; + + await _loggingService.LogInformationAsync($"Configured notification channel: {channel.ChannelType}"); + return true; + } + + public async Task> GetAvailableChannelsAsync() + { + var channels = new List(); + + // SMS Channel + if (await IsChannelEnabledAsync("SMS")) + { + channels.Add(new NotificationChannel + { + ChannelType = "SMS", + IsEnabled = true, + Settings = await GetSmsConfigurationAsync() + }); + } + + // Email Channel + if (await IsChannelEnabledAsync("Email")) + { + channels.Add(new NotificationChannel + { + ChannelType = "Email", + IsEnabled = true, + Settings = await GetEmailConfigurationAsync() + }); + } + + // WeChat Channel + if (await IsChannelEnabledAsync("WeChat")) + { + channels.Add(new NotificationChannel + { + ChannelType = "WeChat", + IsEnabled = true, + Settings = await GetWechatConfigurationAsync() + }); + } + + return channels; + } + + public async Task TestNotificationChannelAsync(NotificationChannel channel) + { + if (channel == null) + throw new ArgumentNullException(nameof(channel)); + + var testMessage = $"This is a test notification from CNC System at {DateTime.Now:yyyy-MM-dd HH:mm:ss}"; + + switch (channel.ChannelType) + { + case "SMS": + await SendSmsNotificationAsync(channel.TestPhoneNumber, testMessage); + break; + case "Email": + await SendEmailNotificationAsync(channel.TestEmail, "CNC System Test", testMessage); + break; + case "WeChat": + await SendWechatNotificationAsync(channel.TestOpenId, testMessage); + break; + default: + throw new ArgumentException($"Unsupported channel type: {channel.ChannelType}"); + } + + await _loggingService.LogInformationAsync($"Test notification sent via {channel.ChannelType}"); + } + + public async Task GetFailedNotificationCountAsync(DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + return await _notificationRepository.GetFailedNotificationsCountAsync(startOfDay, endOfDay); + } + + public async Task ResendFailedNotificationsAsync(int maxRetries = 3) + { + var failedNotifications = await _notificationRepository.GetFailedNotificationsAsync(maxRetries); + + if (!failedNotifications.Any()) + { + await _loggingService.LogInformationAsync("No failed notifications to resend"); + return true; + } + + var successCount = 0; + var retryTasks = failedNotifications.Select(async notification => + { + try + { + await RetryFailedNotificationAsync(notification); + successCount++; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Failed to resend notification {notification.NotificationId}: {ex.Message}", ex); + } + }); + + await Task.WhenAll(retryTasks); + + await _loggingService.LogInformationAsync($"Resent {successCount}/{failedNotifications.Count} failed notifications"); + return successCount > 0; + } + + private async Task> GetNotificationChannelsForAlarm(Alarm alarm) + { + var channels = new List(); + var alarmType = alarm.AlarmType.ToLower(); + + // Get global notification settings + var smsEnabled = await IsChannelEnabledAsync("SMS"); + var emailEnabled = await IsChannelEnabledAsync("Email"); + var wechatEnabled = await IsChannelEnabledAsync("WeChat"); + + // High severity alarms get all channels + if (alarm.AlarmSeverity == AlarmSeverity.Critical || alarm.AlarmSeverity == AlarmSeverity.High) + { + if (smsEnabled) channels.Add(await CreateSmsChannelAsync()); + if (emailEnabled) channels.Add(await CreateEmailChannelAsync()); + if (wechatEnabled) channels.Add(await CreateWechatChannelAsync()); + } + // Medium severity alarms get email and SMS + else if (alarm.AlarmSeverity == AlarmSeverity.Medium) + { + if (emailEnabled) channels.Add(await CreateEmailChannelAsync()); + if (smsEnabled) channels.Add(await CreateSmsChannelAsync()); + } + // Low severity alarms get email only + else if (alarm.AlarmSeverity == AlarmSeverity.Low && emailEnabled) + { + channels.Add(await CreateEmailChannelAsync()); + } + + return channels; + } + + private async Task SendNotificationViaChannelAsync(Alarm alarm, NotificationChannel channel) + { + var subject = $"CNC Alarm: {alarm.AlarmType} - {alarm.DeviceName}"; + var message = CreateAlarmMessage(alarm); + + switch (channel.ChannelType) + { + case "SMS": + await SendSmsNotificationAsync(channel.Recipients.FirstOrDefault(), message); + break; + case "Email": + await SendEmailNotificationAsync(channel.Recipients.FirstOrDefault(), subject, message); + break; + case "WeChat": + await SendWechatNotificationAsync(channel.Recipients.FirstOrDefault(), message); + break; + } + } + + private string CreateAlarmMessage(Alarm alarm) + { + return $@" +🚨 CNC System Alarm Alert 🚨 + +Device: {alarm.DeviceName} ({alarm.DeviceCode}) +Type: {alarm.AlarmType} +Severity: {alarm.AlarmSeverity} +Time: {alarm.CreateTime:yyyy-MM-dd HH:mm:ss} + +Description: {alarm.Title} + +Please take immediate action. +".Trim(); + } + + private async Task GetSmsConfigurationAsync() + { + // Get SMS configuration from database or use defaults + return new NotificationSettings + { + IsEnabled = true, + ApiKey = await _configRepository.GetValueAsync("sms_api_key") ?? "", + ApiSecret = await _configRepository.GetValueAsync("sms_api_secret") ?? "", + Provider = await _configRepository.GetValueAsync("sms_provider") ?? "twilio" + }; + } + + private async Task GetEmailConfigurationAsync() + { + return new NotificationSettings + { + IsEnabled = true, + SmtpServer = await _configRepository.GetValueAsync("smtp_server") ?? "smtp.gmail.com", + SmtpPort = int.Parse(await _configRepository.GetValueAsync("smtp_port") ?? "587"), + Username = await _configRepository.GetValueAsync("smtp_username") ?? "", + Password = await _configRepository.GetValueAsync("smtp_password") ?? "", + UseSsl = bool.Parse(await _configRepository.GetValueAsync("smtp_ssl") ?? "true") + }; + } + + private async Task GetWechatConfigurationAsync() + { + return new NotificationSettings + { + IsEnabled = true, + AppId = await _configRepository.GetValueAsync("wechat_app_id") ?? "", + AppSecret = await _configRepository.GetValueAsync("wechat_app_secret") ?? "", + TemplateId = await _configRepository.GetValueAsync("wechat_template_id") ?? "" + }; + } + + private async Task IsChannelEnabledAsync(string channelType) + { + var configKey = $"notification_{channelType}_enabled"; + var configValue = await _configRepository.GetValueAsync(configKey); + return bool.TryParse(configValue, out bool enabled) && enabled; + } + + private async Task ValidateChannelConfigurationAsync(NotificationChannel channel) + { + // Add validation logic based on channel type + switch (channel.ChannelType) + { + case "SMS": + return !string.IsNullOrEmpty(channel.TestPhoneNumber); + case "Email": + return !string.IsNullOrEmpty(channel.TestEmail); + case "WeChat": + return !string.IsNullOrEmpty(channel.TestOpenId); + default: + return false; + } + } + + private async Task CreateSmsChannelAsync() + { + return new NotificationChannel + { + ChannelType = "SMS", + IsEnabled = true, + Recipients = await GetSmsRecipientsAsync(), + TestPhoneNumber = await _configRepository.GetValueAsync("sms_test_phone") ?? "+1234567890", + Settings = await GetSmsConfigurationAsync() + }; + } + + private async Task CreateEmailChannelAsync() + { + return new NotificationChannel + { + ChannelType = "Email", + IsEnabled = true, + Recipients = await GetEmailRecipientsAsync(), + TestEmail = await _configRepository.GetValueAsync("email_test_address") ?? "test@example.com", + Settings = await GetEmailConfigurationAsync() + }; + } + + private async Task CreateWechatChannelAsync() + { + return new NotificationChannel + { + ChannelType = "WeChat", + IsEnabled = true, + Recipients = await GetWechatRecipientsAsync(), + TestOpenId = await _configRepository.GetValueAsync("wechat_test_openid") ?? "test_openid", + Settings = await GetWechatConfigurationAsync() + }; + } + + private async Task> GetSmsRecipientsAsync() + { + var configValue = await _configRepository.GetValueAsync("sms_recipients"); + return string.IsNullOrEmpty(configValue) ? new List() : configValue.Split(',').Select(s => s.Trim()).ToList(); + } + + private async Task> GetEmailRecipientsAsync() + { + var configValue = await _configRepository.GetValueAsync("email_recipients"); + return string.IsNullOrEmpty(configValue) ? new List() : configValue.Split(',').Select(s => s.Trim()).ToList(); + } + + private async Task> GetWechatRecipientsAsync() + { + var configValue = await _configRepository.GetValueAsync("wechat_recipients"); + return string.IsNullOrEmpty(configValue) ? new List() : configValue.Split(',').Select(s => s.Trim()).ToList(); + } + + private async Task RetryFailedNotificationAsync(AlarmNotification notification) + { + // Retry logic here + await _notificationRepository.MarkAsRetriedAsync(notification.NotificationId); + } + } + + // Supporting interfaces and classes + public interface INotificationSender + { + Task SendAsync(string recipient, string message, NotificationSettings settings); + } + + public class NotificationResult + { + public bool Success { get; set; } + public string ErrorMessage { get; set; } + public string ReferenceId { get; set; } + } + + public class NotificationSettings + { + public bool IsEnabled { get; set; } + public string ApiKey { get; set; } + public string ApiSecret { get; set; } + public string Provider { get; set; } + public string SmtpServer { get; set; } + public int SmtpPort { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public bool UseSsl { get; set; } + public string AppId { get; set; } + public string AppSecret { get; set; } + public string TemplateId { get; set; } + } + + // Additional repository interface for alarm notifications + public interface IAlarmNotificationRepository : IRepository + { + Task> GetNotificationsByDateRangeAsync(DateTime startDate, DateTime endDate); + Task> GetFailedNotificationsAsync(int maxRetries = 3); + Task GetFailedNotificationsCountAsync(DateTime startDate, DateTime endDate); + Task MarkAsRetriedAsync(int notificationId); + Task> GetNotificationsByAlarmAsync(int alarmId); + } +} \ No newline at end of file diff --git a/Haoliang.Core/Services/AlarmRuleService.cs b/Haoliang.Core/Services/AlarmRuleService.cs new file mode 100644 index 0000000..bc0cda1 --- /dev/null +++ b/Haoliang.Core/Services/AlarmRuleService.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Haoliang.Models.System; +using Haoliang.Models.Device; +using Haoliang.Models.DataCollection; +using Haoliang.Data.Repositories; + +namespace Haoliang.Core.Services +{ + public interface IAlarmRuleService + { + Task CreateAlarmRuleAsync(AlarmRule rule); + Task UpdateAlarmRuleAsync(int ruleId, AlarmRule rule); + Task DeleteAlarmRuleAsync(int ruleId); + Task GetAlarmRuleByIdAsync(int ruleId); + Task> GetAllAlarmRulesAsync(); + Task> GetActiveAlarmRulesAsync(); + Task> GetRulesByDeviceAsync(int deviceId); + Task EvaluateAlarmRuleAsync(AlarmRule rule, DeviceCurrentStatus status); + Task GenerateAlarmFromRuleAsync(AlarmRule rule, DeviceCurrentStatus status); + Task TestAlarmRuleAsync(int ruleId); + } + + public class AlarmRuleService : IAlarmRuleService + { + private readonly IAlarmRuleRepository _alarmRuleRepository; + private readonly IDeviceRepository _deviceRepository; + private readonly IAlarmRepository _alarmRepository; + private readonly ILoggingService _loggingService; + + public AlarmRuleService( + IAlarmRuleRepository alarmRuleRepository, + IDeviceRepository deviceRepository, + IAlarmRepository alarmRepository, + ILoggingService loggingService) + { + _alarmRuleRepository = alarmRuleRepository; + _deviceRepository = deviceRepository; + _alarmRepository = alarmRepository; + _loggingService = loggingService; + } + + public async Task CreateAlarmRuleAsync(AlarmRule rule) + { + // Validate rule + if (string.IsNullOrWhiteSpace(rule.RuleName)) + throw new ArgumentException("Rule name is required"); + + if (string.IsNullOrWhiteSpace(rule.Condition)) + throw new ArgumentException("Condition is required"); + + rule.RuleId = 0; // Ensure new rule + rule.CreatedAt = DateTime.Now; + rule.UpdatedAt = DateTime.Now; + + await _alarmRuleRepository.AddAsync(rule); + await _alarmRuleRepository.SaveAsync(); + + await _loggingService.LogInfoAsync($"Created alarm rule: {rule.RuleName}"); + return rule; + } + + public async Task UpdateAlarmRuleAsync(int ruleId, AlarmRule rule) + { + var existingRule = await _alarmRuleRepository.GetByIdAsync(ruleId); + if (existingRule == null) + throw new KeyNotFoundException($"Alarm rule with ID {ruleId} not found"); + + existingRule.RuleName = rule.RuleName; + existingRule.Condition = rule.Condition; + existingRule.AlarmType = rule.AlarmType; + existingRule.AlarmSeverity = rule.AlarmSeverity; + existingRule.IsActive = rule.IsActive; + existingRule.DeviceId = rule.DeviceId; + existingRule.UpdatedAt = DateTime.Now; + + await _alarmRuleRepository.UpdateAsync(existingRule); + await _alarmRuleRepository.SaveAsync(); + + await _loggingService.LogInfoAsync($"Updated alarm rule: {existingRule.RuleName}"); + return existingRule; + } + + public async Task DeleteAlarmRuleAsync(int ruleId) + { + var rule = await _alarmRuleRepository.GetByIdAsync(ruleId); + if (rule == null) + return false; + + await _alarmRuleRepository.DeleteAsync(rule); + await _alarmRuleRepository.SaveAsync(); + + await _loggingService.LogInfoAsync($"Deleted alarm rule: {rule.RuleName}"); + return true; + } + + public async Task GetAlarmRuleByIdAsync(int ruleId) + { + return await _alarmRuleRepository.GetByIdAsync(ruleId); + } + + public async Task> GetAllAlarmRulesAsync() + { + return await _alarmRuleRepository.GetAllAsync(); + } + + public async Task> GetActiveAlarmRulesAsync() + { + return await _alarmRuleRepository.GetActiveRulesAsync(); + } + + public async Task> GetRulesByDeviceAsync(int deviceId) + { + return await _alarmRuleRepository.GetRulesByDeviceAsync(deviceId); + } + + public async Task EvaluateAlarmRuleAsync(AlarmRule rule, DeviceCurrentStatus status) + { + if (!rule.IsActive) + return false; + + try + { + // Simple condition evaluation (in real implementation, use expression parser) + var condition = rule.Condition.ToLower(); + + if (condition.Contains("device_offline") && !status.IsOnline) + return true; + + if (condition.Contains("device_error") && status.Status == "Error") + return true; + + if (condition.Contains("high_temperature") && + status.Tags?.Any(t => t.Id == "temperature" && Convert.ToDouble(t.Value) > 80) == true) + return true; + + if (condition.Contains("low_production") && + status.CumulativeCount < 10) + return true; + + // Custom condition evaluation + if (condition.Contains("running_time") && + status.IsRunning && + (DateTime.Now - status.RecordTime).TotalMinutes > 120) + return true; + + return false; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Error evaluating alarm rule {rule.RuleName}: {ex.Message}", ex); + return false; + } + } + + public async Task GenerateAlarmFromRuleAsync(AlarmRule rule, DeviceCurrentStatus status) + { + var device = await _deviceRepository.GetByIdAsync(status.DeviceId); + if (device == null) + throw new InvalidOperationException("Device not found"); + + var alarm = new Alarm + { + DeviceId = status.DeviceId, + DeviceCode = device.DeviceCode, + DeviceName = device.DeviceName, + AlarmType = rule.AlarmType.ToString(), + AlarmSeverity = rule.AlarmSeverity, + Title = $"Alarm triggered by rule: {rule.RuleName}", + Description = $"Condition: {rule.Condition}", + AlarmStatus = AlarmStatus.Active, + CreateTime = DateTime.Now, + IsActive = true + }; + + await _alarmRepository.AddAsync(alarm); + await _alarmRepository.SaveAsync(); + + await _loggingService.LogWarningAsync($"Generated alarm: {alarm.Title} for device {device.DeviceCode}"); + return alarm; + } + + public async Task TestAlarmRuleAsync(int ruleId) + { + var rule = await _alarmRuleRepository.GetByIdAsync(ruleId); + if (rule == null) + throw new KeyNotFoundException($"Alarm rule with ID {ruleId} not found"); + + // Get a sample device status for testing + var devices = await _deviceRepository.GetAllAsync(); + var sampleDevice = devices.FirstOrDefault(); + + if (sampleDevice == null) + { + await _loggingService.LogWarningAsync($"No devices available to test alarm rule {rule.RuleName}"); + return; + } + + var sampleStatus = new DeviceCurrentStatus + { + DeviceId = sampleDevice.Id, + DeviceCode = sampleDevice.DeviceCode, + DeviceName = sampleDevice.DeviceName, + IsOnline = true, + IsAvailable = true, + Status = "Running", + IsRunning = true, + NCProgram = "TEST_PROGRAM", + CumulativeCount = 50, + OperatingMode = "Auto", + RecordTime = DateTime.Now, + Tags = new List + { + new TagData { Id = "temperature", Value = 85.0, Time = DateTime.Now }, + new TagData { Id = "pressure", Value = 120, Time = DateTime.Now } + } + }; + + var shouldTrigger = await EvaluateAlarmRuleAsync(rule, sampleStatus); + + if (shouldTrigger) + { + await GenerateAlarmFromRuleAsync(rule, sampleStatus); + await _loggingService.LogInformationAsync($"Alarm rule test: {rule.RuleName} would trigger an alarm"); + } + else + { + await _loggingService.LogInformationAsync($"Alarm rule test: {rule.RuleName} would not trigger an alarm"); + } + } + } + + // Additional repository interface for alarm rules + public interface IAlarmRuleRepository : IRepository + { + Task> GetActiveRulesAsync(); + Task> GetRulesByDeviceAsync(int deviceId); + Task> GetRulesByAlarmTypeAsync(AlarmType alarmType); + Task RuleExistsAsync(string ruleName); + Task> GetEnabledRulesAsync(); + } +} \ No newline at end of file diff --git a/Haoliang.Core/Services/CacheService.cs b/Haoliang.Core/Services/CacheService.cs new file mode 100644 index 0000000..9723e4d --- /dev/null +++ b/Haoliang.Core/Services/CacheService.cs @@ -0,0 +1,655 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; +using Haoliang.Core.Services; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Models.System; + +namespace Haoliang.Core.Services +{ + public interface ICacheService + { + /// + /// Get cached value or execute factory if not exists + /// + Task GetOrSetAsync(string key, Func> factory, MemoryCacheEntryOptions options = null); + + /// + /// Get cached value synchronously + /// + T Get(string key); + + /// + /// Set cache value + /// + void Set(string key, T value, MemoryCacheEntryOptions options = null); + + /// + /// Remove cached value + /// + bool Remove(string key); + + /// + /// Check if key exists in cache + /// + bool Exists(string key); + + /// + /// Clear all cache + /// + void Clear(); + + /// + /// Get cache statistics + /// + CacheStatistics GetStatistics(); + + /// + /// Get cache keys matching pattern + /// + IEnumerable GetKeys(string pattern); + + /// + /// Refresh cached value + /// + bool Refresh(string key); + } + + public class CacheStatistics + { + public long TotalItems { get; set; } + public long Hits { get; set; } + public long Misses { get; set; } + public double HitRate => Hits + Misses > 0 ? (double)Hits / (Hits + Misses) : 0; + public long MemoryUsageBytes { get; set; } + public DateTime LastCleared { get; set; } + public Dictionary ItemsByType { get; set; } + } + + public class MemoryCacheService : ICacheService + { + private readonly IMemoryCache _memoryCache; + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private long _hits = 0; + private long _misses = 0; + private long _memoryUsage = 0; + + public MemoryCacheService(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public Task GetOrSetAsync(string key, Func> factory, MemoryCacheEntryOptions options = null) + { + return Task.Run(async () => + { + // Try to get from cache first + if (_memoryCache.TryGetValue(key, out T value)) + { + Interlocked.Increment(ref _hits); + return value; + } + + Interlocked.Increment(ref _misses); + + // Create new value + value = await factory(); + + if (value != null) + { + // Set cache options if not provided + options = options ?? new MemoryCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2), + Size = 1024 // 1KB + }; + + // Set cache with lock + _lock.EnterWriteLock(); + try + { + _memoryCache.Set(key, value, options); + UpdateMemoryUsage(options.Size.GetValueOrDefault(1024)); + } + finally + { + _lock.ExitWriteLock(); + } + } + + return value; + }); + } + + public T Get(string key) + { + if (_memoryCache.TryGetValue(key, out T value)) + { + Interlocked.Increment(ref _hits); + return value; + } + + Interlocked.Increment(ref _misses); + return default(T); + } + + public void Set(string key, T value, MemoryCacheEntryOptions options = null) + { + if (value == null) + return; + + options = options ?? new MemoryCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2), + Size = 1024 // 1KB + }; + + _lock.EnterWriteLock(); + try + { + _memoryCache.Set(key, value, options); + UpdateMemoryUsage(options.Size.GetValueOrDefault(1024)); + } + finally + { + _lock.ExitWriteLock(); + } + } + + public bool Remove(string key) + { + _lock.EnterWriteLock(); + try + { + if (_memoryCache.TryGetValue(key, out object value)) + { + _memoryCache.Remove(key); + UpdateMemoryUsage(-(GetEstimatedSize(value))); + return true; + } + return false; + } + finally + { + _lock.ExitWriteLock(); + } + } + + public bool Exists(string key) + { + return _memoryCache.TryGetValue(key, out _); + } + + public void Clear() + { + _lock.EnterWriteLock(); + try + { + // This is a simplified implementation + // In a real scenario, you might need a more sophisticated way to clear the cache + _memoryCache.Compact(1.0); // Remove all entries + _memoryUsage = 0; + Interlocked.Exchange(ref _hits, 0); + Interlocked.Exchange(ref _misses, 0); + } + finally + { + _lock.ExitWriteLock(); + } + } + + public CacheStatistics GetStatistics() + { + _lock.EnterReadLock(); + try + { + var stats = new CacheStatistics + { + TotalItems = GetCacheSize(), + Hits = _hits, + Misses = _misses, + MemoryUsageBytes = _memoryUsage, + LastCleared = DateTime.Now, + ItemsByType = new Dictionary() + }; + + // Count items by type (simplified) + var deviceCacheCount = GetKeys("device:*").Count(); + var productionCacheCount = GetKeys("production:*").Count(); + var configCacheCount = GetKeys("config:*").Count(); + var templateCacheCount = GetKeys("template:*").Count(); + + stats.ItemsByType["device"] = deviceCacheCount; + stats.ItemsByType["production"] = productionCacheCount; + stats.ItemsByType["config"] = configCacheCount; + stats.ItemsByType["template"] = templateCacheCount; + + return stats; + } + finally + { + _lock.ExitReadLock(); + } + } + + public IEnumerable GetKeys(string pattern) + { + // This is a simplified implementation + // In a real scenario, you might need a more sophisticated key pattern matching + return _memoryCache.Keys + .Cast() + .Where(key => key.StartsWith(pattern.Replace("*", ""))); + } + + public bool Refresh(string key) + { + if (!_memoryCache.TryGetValue(key, out T value)) + return false; + + // Remove and re-add to refresh + _lock.EnterWriteLock(); + try + { + _memoryCache.Remove(key); + _memoryCache.Set(key, value, new MemoryCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) + }); + return true; + } + finally + { + _lock.ExitWriteLock(); + } + } + + #region Private Methods + + private int GetCacheSize() + { + // This is an approximation + return _memoryCache.Count; + } + + private void UpdateMemoryUsage(long delta) + { + Interlocked.Add(ref _memoryUsage, delta); + } + + private int GetEstimatedSize(object obj) + { + // Simplified size estimation + if (obj == null) return 0; + + var type = obj.GetType(); + if (type.IsValueType || type == typeof(string)) + { + return System.Text.Json.JsonSerializer.Serialize(obj).Length; + } + + // For complex objects, estimate based on type + return 1024; // Default 1KB for complex objects + } + + #endregion + } + + // Extension methods for common caching patterns + public static class CacheServiceExtensions + { + /// + /// Cache device information + /// + public static Task GetOrSetDeviceAsync(this ICacheService cache, int deviceId, + Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions(); + if (expiration.HasValue) + { + options.SlidingExpiration = expiration.Value; + options.AbsoluteExpirationRelativeToNow = expiration.Value + TimeSpan.FromMinutes(30); + } + else + { + options.SlidingExpiration = TimeSpan.FromMinutes(30); + options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2); + } + + return cache.GetOrSetAsync($"device:{deviceId}", factory, options); + } + + /// + /// Cache device list + /// + public static Task> GetOrSetAllDevicesAsync(this ICacheService cache, + Func>> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(15), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) + }; + + return cache.GetOrSetAsync("devices:all", factory, options); + } + + /// + /// Cache device status + /// + public static Task GetOrSetDeviceStatusAsync(this ICacheService cache, int deviceId, + Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(5), + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) + }; + + return cache.GetOrSetAsync($"device:status:{deviceId}", factory, options); + } + + /// + /// Cache production records + /// + public static Task> GetOrSetProductionRecordsAsync(this ICacheService cache, + int deviceId, DateTime date, Func>> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(10), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) + }; + + return cache.GetOrSetAsync($"production:records:{deviceId}:{date:yyyy-MM-dd}", factory, options); + } + + /// + /// Cache production summary + /// + public static Task GetOrSetProductionSummaryAsync(this ICacheService cache, + int deviceId, string programName, Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) + }; + + return cache.GetOrSetAsync($"production:summary:{deviceId}:{programName}", factory, options); + } + + /// + /// Cache system configuration + /// + public static Task GetOrSetSystemConfigurationAsync(this ICacheService cache, + Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromHours(1), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) + }; + + return cache.GetOrSetAsync("config:system", factory, options); + } + + /// + /// Cache template + /// + public static Task GetOrSetTemplateAsync(this ICacheService cache, + int templateId, Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) + }; + + return cache.GetOrSetAsync($"template:{templateId}", factory, options); + } + + /// + /// Cache template list + /// + public static Task> GetOrSetAllTemplatesAsync(this ICacheService cache, + Func>> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) + }; + + return cache.GetOrSetAsync("templates:all", factory, options); + } + + /// + /// Cache alert configuration + /// + public static Task GetOrSetAlertConfigurationAsync(this ICacheService cache, + Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) + }; + + return cache.GetOrSetAsync("config:alerts", factory, options); + } + + /// + /// Cache dashboard summary + /// + public static Task GetOrSetDashboardSummaryAsync(this ICacheService cache, + DateTime date, Func> factory, TimeSpan? expiration = null) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = expiration ?? TimeSpan.FromMinutes(10), + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) + }; + + return cache.GetOrSetAsync($"dashboard:summary:{date:yyyy-MM-dd}", factory, options); + } + + /// + /// Invalidate device-related cache + /// + public static void InvalidateDeviceCache(this ICacheService cache, int deviceId, params string[] additionalKeys) + { + var keys = new[] + { + $"device:{deviceId}", + $"device:status:{deviceId}", + $"production:records:{deviceId}", + $"production:summary:{deviceId}" + }; + + foreach (var key in keys.Concat(additionalKeys)) + { + cache.Remove(key); + } + } + + /// + /// Invalidate production-related cache + /// + public static void InvalidateProductionCache(this ICacheService cache, int deviceId, string programName, DateTime date) + { + cache.Remove($"production:records:{deviceId}:{date:yyyy-MM-dd}"); + cache.Remove($"production:summary:{deviceId}:{programName}"); + } + + /// + /// Invalidate template-related cache + /// + public static void InvalidateTemplateCache(this ICacheService cache, int templateId) + { + cache.Remove($"template:{templateId}"); + cache.Remove("templates:all"); + } + + /// + /// Invalidate system configuration cache + /// + public static void InvalidateSystemConfigCache(this ICacheService cache) + { + cache.Remove("config:system"); + cache.Remove("config:alerts"); + cache.Remove("dashboard:summary"); + } + + /// + /// Invalidate dashboard cache + /// + public static void InvalidateDashboardCache(this ICacheService cache, DateTime date) + { + cache.Remove($"dashboard:summary:{date:yyyy-MM-dd}"); + } + + /// + /// Get or set with sliding expiration for frequently accessed data + /// + public static Task GetOrSetWithSlidingExpiration(this ICacheService cache, string key, + Func> factory, TimeSpan slidingExpiration) + { + var options = new MemoryCacheEntryOptions + { + SlidingExpiration = slidingExpiration + }; + + return cache.GetOrSetAsync(key, factory, options); + } + + /// + /// Get or set with absolute expiration for time-sensitive data + /// + public static Task GetOrSetWithAbsoluteExpiration(this ICacheService cache, string key, + Func> factory, TimeSpan absoluteExpiration) + { + var options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = absoluteExpiration + }; + + return cache.GetOrSetAsync(key, factory, options); + } + + /// + /// Get or set with priority for important data + /// + public static Task GetOrSetWithPriority(this ICacheService cache, string key, + Func> factory, CacheItemPriority priority) + { + var options = new MemoryCacheEntryOptions + { + Priority = priority + }; + + return cache.GetOrSetAsync(key, factory, options); + } + } + + /// + /// Cache service for distributed caching + /// + public interface IDistributedCacheService : ICacheService + { + /// + /// Get distributed lock + /// + Task AcquireLockAsync(string key, TimeSpan timeout); + + /// + /// Refresh distributed cache + /// + Task RefreshAsync(string key); + + /// + /// Get distributed cache statistics + /// + Task GetDistributedStatisticsAsync(); + } + + public interface IDistributedLock : IDisposable + { + bool IsAcquired { get; } + void Release(); + } + + public class DistributedCacheStatistics : CacheStatistics + { + public int ConnectedNodes { get; set; } + public long NetworkCalls { get; set; } + public long SynchronizationErrors { get; set; } + } + + /// + /// Cache manager for managing multiple cache instances + /// + public interface ICacheManager + { + ICacheService LocalCache { get; } + IDistributedCacheService DistributedCache { get; } + void ConfigureCacheSettings(CacheSettings settings); + void InitializeCaches(); + } + + public class CacheSettings + { + public bool EnableLocalCache { get; set; } = true; + public bool EnableDistributedCache { get; set; } = false; + public TimeSpan DefaultSlidingExpiration { get; set; } = TimeSpan.FromMinutes(30); + public TimeSpan DefaultAbsoluteExpiration { get; set; } = TimeSpan.FromHours(2); + public long MaxMemorySizeBytes { get; set; } = 1024 * 1024 * 100; // 100MB + public CacheEvictionPolicy EvictionPolicy { get; set; } = CacheEvictionPolicy.LRU; + public bool EnableCacheLogging { get; set; } = false; + public TimeSpan CacheRefreshInterval { get; set; } = TimeSpan.FromMinutes(5); + } + + public enum CacheEvictionPolicy + { + LRU, // Least Recently Used + LFU, // Least Frequently Used + FIFO, // First In First Out + Random + } + + public class CacheManager : ICacheManager + { + public ICacheService LocalCache { get; private set; } + public IDistributedCacheService DistributedCache { get; private set; } + + public CacheManager(ICacheService localCache, IDistributedCacheService distributedCache) + { + LocalCache = localCache; + DistributedCache = distributedCache; + } + + public void ConfigureCacheSettings(CacheSettings settings) + { + // Configure cache settings based on provided configuration + // This would involve setting up the cache instances with the specified settings + } + + public void InitializeCaches() + { + // Initialize caches with default settings + ClearAllCaches(); + } + + public void ClearAllCaches() + { + LocalCache?.Clear(); + DistributedCache?.Clear(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Core/Services/DeviceStateMachine.cs b/Haoliang.Core/Services/DeviceStateMachine.cs new file mode 100644 index 0000000..84be102 --- /dev/null +++ b/Haoliang.Core/Services/DeviceStateMachine.cs @@ -0,0 +1,997 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Haoliang.Core.Services; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.System; +using Haoliang.Models.Common; + +namespace Haoliang.Core.Services +{ + public interface IDeviceStateMachine + { + /// + /// Get current state of device + /// + DeviceState GetCurrentState(int deviceId); + + /// + /// Check if device can transition to target state + /// + Task CanTransitionAsync(int deviceId, DeviceState targetState); + + /// + /// Transition device to new state + /// + Task TransitionToStateAsync(int deviceId, DeviceState targetState, object context = null); + + /// + /// Trigger device event + /// + Task TriggerEventAsync(int deviceId, DeviceEvent deviceEvent, object context = null); + + /// + /// Get device state history + /// + Task> GetStateHistoryAsync(int deviceId, DateTime? startDate = null, DateTime? endDate = null); + + /// + /// Get device state statistics + /// + Task GetStateStatisticsAsync(int deviceId, DateTime? startDate = null, DateTime? endDate = null); + + /// + /// Force device to specific state + /// + Task ForceStateAsync(int deviceId, DeviceState targetState, string reason); + + /// + /// Validate device state + /// + Task ValidateStateAsync(int deviceId); + + /// + /// Register state change handler + /// + void RegisterStateHandler(StateChangeHandler handler); + + /// + /// Unregister state change handler + /// + void UnregisterStateHandler(StateChangeHandler handler); + } + + public class DeviceStateMachine : IDeviceStateMachine, IHostedService + { + private readonly IDeviceRepository _deviceRepository; + private readonly IDeviceCollectionService _collectionService; + private readonly IAlarmService _alarmService; + private readonly ICacheService _cacheService; + private readonly Timer _stateCheckTimer; + private readonly ConcurrentDictionary _deviceStates = new ConcurrentDictionary(); + private readonly List _stateHandlers = new List(); + private readonly object _lock = new object(); + + public DeviceStateMachine( + IDeviceRepository deviceRepository, + IDeviceCollectionService collectionService, + IAlarmService alarmService, + ICacheService cacheService) + { + _deviceRepository = deviceRepository; + _collectionService = collectionService; + _alarmService = alarmService; + _cacheService = cacheService; + + // Start timer for periodic state checks + _stateCheckTimer = new Timer(CheckDeviceStates, null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); + } + + public DeviceState GetCurrentState(int deviceId) + { + if (_deviceStates.TryGetValue(deviceId, out var context)) + { + return context.CurrentState; + } + return DeviceState.Unknown; + } + + public async Task CanTransitionAsync(int deviceId, DeviceState targetState) + { + var currentState = GetCurrentState(deviceId); + return await CanTransitionFromAsync(currentState, targetState, deviceId); + } + + public async Task TransitionToStateAsync(int deviceId, DeviceState targetState, object context = null) + { + var result = new DeviceStateTransitionResult + { + DeviceId = deviceId, + FromState = GetCurrentState(deviceId), + ToState = targetState, + Success = false, + Message = "", + Timestamp = DateTime.UtcNow + }; + + try + { + // Validate transition + if (!await CanTransitionAsync(deviceId, targetState)) + { + result.Message = $"Cannot transition from {result.FromState} to {targetState}"; + return result; + } + + // Get device + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) + { + result.Message = "Device not found"; + return result; + } + + // Create transition context + var transitionContext = new DeviceStateTransitionContext + { + DeviceId = deviceId, + Device = device, + FromState = result.FromState, + ToState = targetState, + Context = context, + Timestamp = DateTime.UtcNow + }; + + // Execute exit actions for current state + var exitResult = await ExecuteStateActionsAsync(deviceId, result.FromState, transitionContext, true); + if (!exitResult.Success) + { + result.Message = $"Failed to exit {result.FromState}: {exitResult.Message}"; + return result; + } + + // Execute enter actions for target state + var enterResult = await ExecuteStateActionsAsync(deviceId, targetState, transitionContext, false); + if (!enterResult.Success) + { + result.Message = $"Failed to enter {targetState}: {enterResult.Message}"; + // Attempt to revert to original state + await ExecuteStateActionsAsync(deviceId, result.FromState, transitionContext, true); + return result; + } + + // Update device state + lock (_lock) + { + var deviceContext = _deviceStates.GetOrAdd(deviceId, new DeviceStateContext + { + CurrentState = targetState, + PreviousState = result.FromState, + StateChangedAt = DateTime.UtcNow, + Context = context + }); + + deviceContext.CurrentState = targetState; + deviceContext.PreviousState = result.FromState; + deviceContext.StateChangedAt = DateTime.UtcNow; + deviceContext.Context = context; + } + + // Record state change in history + await RecordStateChangeAsync(deviceId, result.FromState, targetState, context); + + // Notify handlers + await NotifyStateHandlersAsync(deviceId, result.FromState, targetState, transitionContext); + + // Update device status + await UpdateDeviceStatusAsync(device, targetState); + + result.Success = true; + result.Message = $"Successfully transitioned from {result.FromState} to {targetState}"; + } + catch (Exception ex) + { + result.Message = $"Error during state transition: {ex.Message}"; + } + + return result; + } + + public async Task TriggerEventAsync(int deviceId, DeviceEvent deviceEvent, object context = null) + { + var currentState = GetCurrentState(deviceId); + var eventConfig = GetEventConfiguration(deviceEvent, currentState); + + if (eventConfig == null) + { + return new DeviceStateTransitionResult + { + DeviceId = deviceId, + FromState = currentState, + ToState = currentState, + Success = false, + Message = $"No event handler configured for {deviceEvent} in state {currentState}", + Timestamp = DateTime.UtcNow + }; + } + + // Check event conditions + if (!await EvaluateEventConditionsAsync(deviceId, deviceEvent, eventConfig.Conditions)) + { + return new DeviceStateTransitionResult + { + DeviceId = deviceId, + FromState = currentState, + ToState = currentState, + Success = false, + Message = $"Event conditions not met for {deviceEvent}", + Timestamp = DateTime.UtcNow + }; + } + + // Transition to target state + return await TransitionToStateAsync(deviceId, eventConfig.TargetState, context); + } + + public async Task> GetStateHistoryAsync(int deviceId, DateTime? startDate = null, DateTime? endDate = null) + { + var history = await _deviceRepository.GetDeviceStateHistoryAsync(deviceId, startDate, endDate); + + // Add current state if not in history + if (!history.Any(h => h.State == GetCurrentState(deviceId))) + { + history.Add(new DeviceStateHistory + { + DeviceId = deviceId, + State = GetCurrentState(deviceId), + ChangedAt = DateTime.UtcNow, + ChangedBy = "System", + Notes = "Current state" + }); + } + + return history.OrderBy(h => h.ChangedAt).ToList(); + } + + public async Task GetStateStatisticsAsync(int deviceId, DateTime? startDate = null, DateTime? endDate = null) + { + var history = await GetStateHistoryAsync(deviceId, startDate, endDate); + var statistics = new DeviceStateStatistics + { + DeviceId = deviceId, + PeriodStart = startDate ?? DateTime.UtcNow.AddDays(-7), + PeriodEnd = endDate ?? DateTime.UtcNow, + StateTransitions = history.Count - 1, // Excluding initial state + StateDurations = new Dictionary(), + StateCounts = new Dictionary(), + LastStateChange = history.LastOrDefault()?.ChangedAt + }; + + // Calculate state durations + for (int i = 0; i < history.Count - 1; i++) + { + var fromState = history[i].State; + var toState = history[i + 1].State; + var duration = history[i + 1].ChangedAt - history[i].ChangedAt; + + if (!statistics.StateDurations.ContainsKey(fromState)) + { + statistics.StateDurations[fromState] = TimeSpan.Zero; + } + statistics.StateDurations[fromState] += duration; + + if (!statistics.StateCounts.ContainsKey(fromState)) + { + statistics.StateCounts[fromState] = 0; + } + statistics.StateCounts[fromState]++; + } + + // Add current state duration + if (history.Any()) + { + var currentState = GetCurrentState(deviceId); + var lastState = history.Last(); + var currentDuration = DateTime.UtcNow - lastState.ChangedAt; + + if (!statistics.StateDurations.ContainsKey(currentState)) + { + statistics.StateDurations[currentState] = TimeSpan.Zero; + } + statistics.StateDurations[currentState] += currentDuration; + + if (!statistics.StateCounts.ContainsKey(currentState)) + { + statistics.StateCounts[currentState] = 0; + } + statistics.StateCounts[currentState]++; + } + + return statistics; + } + + public async Task ForceStateAsync(int deviceId, DeviceState targetState, string reason) + { + // Create force transition context + var forceContext = new { Forced = true, Reason = reason }; + + var result = await TransitionToStateAsync(deviceId, targetState, forceContext); + + if (!result.Success) + { + // If normal transition fails, create a force entry in history + await RecordStateChangeAsync(deviceId, GetCurrentState(deviceId), targetState, forceContext, true); + + // Update in-memory state + lock (_lock) + { + var deviceContext = _deviceStates.GetOrAdd(deviceId, new DeviceStateContext()); + deviceContext.CurrentState = targetState; + deviceContext.PreviousState = GetCurrentState(deviceId); + deviceContext.StateChangedAt = DateTime.UtcNow; + deviceContext.Context = forceContext; + } + + result.Success = true; + result.Message = $"Forced state transition to {targetState}: {reason}"; + } + + return result; + } + + public async Task ValidateStateAsync(int deviceId) + { + var result = new DeviceValidationResult + { + DeviceId = deviceId, + IsValid = true, + Issues = new List(), + CurrentState = GetCurrentState(deviceId), + Timestamp = DateTime.UtcNow + }; + + try + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) + { + result.IsValid = false; + result.Issues.Add("Device not found"); + return result; + } + + // Check if device state matches actual status + var actualStatus = await _collectionService.GetDeviceCurrentStatusAsync(deviceId); + var expectedState = TranslateStatusToDeviceState(actualStatus); + + if (expectedState != result.CurrentState) + { + result.IsValid = false; + result.Issues.Add($"State mismatch: expected {expectedState}, actual {result.CurrentState}"); + } + + // Validate state-specific rules + var stateValidation = await ValidateStateRulesAsync(deviceId, result.CurrentState); + if (!stateValidation.IsValid) + { + result.IsValid = false; + result.Issues.AddRange(stateValidation.Issues); + } + + // Check for state timeouts + var stateTimeout = await CheckStateTimeoutAsync(deviceId, result.CurrentState); + if (stateTimeout.IsTimeout) + { + result.IsValid = false; + result.Issues.Add($"State timeout: {result.CurrentState} exceeded maximum duration"); + } + } + catch (Exception ex) + { + result.IsValid = false; + result.Issues.Add($"Validation error: {ex.Message}"); + } + + return result; + } + + public void RegisterStateHandler(StateChangeHandler handler) + { + lock (_lock) + { + _stateHandlers.Add(handler); + } + } + + public void UnregisterStateHandler(StateChangeHandler handler) + { + lock (_lock) + { + _stateHandlers.Remove(handler); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + // Initialize device states + return InitializeDeviceStatesAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _stateCheckTimer?.Dispose(); + return Task.CompletedTask; + } + + #region Private Methods + + private async Task InitializeDeviceStatesAsync() + { + var devices = await _deviceRepository.GetAllDevicesAsync(); + + foreach (var device in devices) + { + var stateContext = new DeviceStateContext + { + CurrentState = DeviceState.Unknown, + PreviousState = DeviceState.Unknown, + StateChangedAt = DateTime.UtcNow, + Context = null + }; + + // Get current device status to determine state + var status = await _collectionService.GetDeviceCurrentStatusAsync(device.Id); + stateContext.CurrentState = TranslateStatusToDeviceState(status); + + _deviceStates.AddOrUpdate(device.Id, stateContext, (key, existing) => stateContext); + } + } + + private async Task CheckDeviceStates(object state) + { + try + { + var devices = await _deviceRepository.GetAllActiveDevicesAsync(); + + foreach (var device in devices) + { + var validationResult = await ValidateStateAsync(device.Id); + if (!validationResult.IsValid) + { + // Handle invalid state + await HandleInvalidStateAsync(device.Id, validationResult); + } + + // Check for state timeouts + var stateTimeout = await CheckStateTimeoutAsync(device.Id, GetCurrentState(device.Id)); + if (stateTimeout.IsTimeout) + { + await HandleStateTimeoutAsync(device.Id, stateTimeout); + } + } + } + catch (Exception ex) + { + // Log error + Console.WriteLine($"Error checking device states: {ex.Message}"); + } + } + + private async Task ExecuteStateActionsAsync( + int deviceId, + DeviceState state, + DeviceStateTransitionContext context, + bool isExit) + { + var actions = isExit ? GetExitActions(state) : GetEnterActions(state); + + foreach (var action in actions) + { + try + { + var result = await action.ExecuteAsync(deviceId, context); + if (!result.Success) + { + return new DeviceStateTransitionResult + { + DeviceId = deviceId, + FromState = context.FromState, + ToState = context.ToState, + Success = false, + Message = result.Message, + Timestamp = DateTime.UtcNow + }; + } + } + catch (Exception ex) + { + return new DeviceStateTransitionResult + { + DeviceId = deviceId, + FromState = context.FromState, + ToState = context.ToState, + Success = false, + Message = $"Error executing action: {ex.Message}", + Timestamp = DateTime.UtcNow + }; + } + } + + return new DeviceStateTransitionResult + { + DeviceId = deviceId, + FromState = context.FromState, + ToState = context.ToState, + Success = true, + Message = "Actions executed successfully", + Timestamp = DateTime.UtcNow + }; + } + + private List GetExitActions(DeviceState state) + { + return GetStateActions(state, "exit"); + } + + private List GetEnterActions(DeviceState state) + { + return GetStateActions(state, "enter"); + } + + private List GetStateActions(DeviceState state, string actionType) + { + // This would typically come from configuration or database + // For now, return basic actions + var actions = new List(); + + switch (state) + { + case DeviceState.Running: + if (actionType == "exit") + { + actions.Add(new StopProductionAction()); + } + else + { + actions.Add(new StartProductionAction()); + } + break; + + case DeviceState.Idle: + if (actionType == "enter") + { + actions.Add(new LogIdleAction()); + } + break; + + case DeviceState.Error: + if (actionType == "enter") + { + actions.Add(new NotifyErrorAction()); + } + break; + + default: + actions.Add(new LogStateAction(actionType, state)); + break; + } + + return actions; + } + + private async Task CanTransitionFromAsync(DeviceState fromState, DeviceState targetState, int deviceId) + { + var allowedTransitions = GetAllowedTransitions(fromState); + return allowedTransitions.Contains(targetState); + } + + private List GetAllowedTransitions(DeviceState fromState) + { + // Define state transition rules + var transitions = new Dictionary> + { + [DeviceState.Unknown] = new List { DeviceState.Offline, DeviceState.Idle }, + [DeviceState.Offline] = new List { DeviceState.Online, DeviceState.Unknown }, + [DeviceState.Online] = new List { DeviceState.Idle, DeviceState.Running, DeviceState.Error, DeviceState.Maintenance }, + [DeviceState.Idle] = new List { DeviceState.Running, DeviceState.Offline, DeviceState.Maintenance }, + [DeviceState.Running] = new List { DeviceState.Idle, DeviceState.Error, DeviceState.Stopped, DeviceState.Maintenance }, + [DeviceState.Error] = new List { DeviceState.Idle, DeviceState.Maintenance, DeviceState.Unknown }, + [DeviceState.Maintenance] = new List { DeviceState.Idle, DeviceState.Offline, DeviceState.Unknown }, + [DeviceState.Stopped] = new List { DeviceState.Idle, DeviceState.Offline } + }; + + return transitions.GetValueOrDefault(fromState, new List()); + } + + private EventConfig GetEventConfiguration(DeviceEvent deviceEvent, DeviceState currentState) + { + // This would typically come from configuration + var eventConfigs = new Dictionary<(DeviceEvent, DeviceState), EventConfig> + { + [(DeviceEvent.Start, DeviceState.Idle)] = new EventConfig + { + TargetState = DeviceState.Running, + Conditions = new List>> + { + async deviceId => await IsDeviceReadyForProduction(deviceId) + } + }, + + [(DeviceEvent.Stop, DeviceState.Running)] = new EventConfig + { + TargetState = DeviceState.Idle, + Conditions = new List>> + { + async deviceId => await IsProductionComplete(deviceId) + } + }, + + [(DeviceEvent.Error, DeviceState.Running)] = new EventConfig + { + TargetState = DeviceState.Error, + Conditions = new List>> + { + async deviceId => await HasDeviceError(deviceId) + } + }, + + [(DeviceEvent.Resume, DeviceState.Maintenance)] = new EventConfig + { + TargetState = DeviceState.Idle, + Conditions = new List>> + { + async deviceId => await IsMaintenanceComplete(deviceId) + } + } + }; + + return eventConfigs.GetValueOrDefault((deviceEvent, currentState)); + } + + private async Task EvaluateEventConditionsAsync(int deviceId, DeviceEvent deviceEvent, List>> conditions) + { + if (conditions == null || !conditions.Any()) + return true; + + foreach (var condition in conditions) + { + var conditionResult = await condition(deviceId); + if (!conditionResult) + return false; + } + + return true; + } + + private async Task RecordStateChangeAsync(int deviceId, DeviceState fromState, DeviceState toState, object context, bool isForced = false) + { + var history = new DeviceStateHistory + { + DeviceId = deviceId, + FromState = fromState, + ToState = toState, + ChangedAt = DateTime.UtcNow, + ChangedBy = isForced ? "System (Forced)" : "System", + Notes = isForced ? $"Forced transition: {JsonSerializer(context)}" : JsonSerializer(context) + }; + + await _deviceRepository.AddDeviceStateHistoryAsync(history); + } + + private async Task NotifyStateHandlersAsync(int deviceId, DeviceState fromState, DeviceState toState, DeviceStateTransitionContext context) + { + var handlersCopy = _stateHandlers.ToList(); // Copy to avoid modification during iteration + + foreach (var handler in handlersCopy) + { + try + { + await handler(deviceId, fromState, toState, context); + } + catch (Exception ex) + { + // Log error but continue with other handlers + Console.WriteLine($"Error in state handler: {ex.Message}"); + } + } + } + + private async Task UpdateDeviceStatusAsync(CNCDevice device, DeviceState state) + { + // Update device status in database + device.Status = TranslateStateToDeviceStatus(state); + await _deviceRepository.UpdateDeviceAsync(device); + + // Update cache + _cacheService.InvalidateDeviceCache(device.Id); + } + + private DeviceState TranslateStatusToDeviceState(DeviceCurrentStatus status) + { + if (status == null || status.Status == DeviceStatus.Unknown) + return DeviceState.Unknown; + + return status.Status switch + { + DeviceStatus.Offline => DeviceState.Offline, + DeviceStatus.Online => DeviceState.Online, + DeviceStatus.Idle => DeviceState.Idle, + DeviceStatus.Running => DeviceState.Running, + DeviceStatus.Error => DeviceState.Error, + DeviceStatus.Maintenance => DeviceState.Maintenance, + DeviceStatus.Stopped => DeviceState.Stopped, + _ => DeviceState.Unknown + }; + } + + private DeviceStatus TranslateStateToDeviceStatus(DeviceState state) + { + return state switch + { + DeviceState.Offline => DeviceStatus.Offline, + DeviceState.Online => DeviceStatus.Online, + DeviceState.Idle => DeviceStatus.Idle, + DeviceState.Running => DeviceStatus.Running, + DeviceState.Error => DeviceStatus.Error, + DeviceState.Maintenance => DeviceStatus.Maintenance, + DeviceState.Stopped => DeviceStatus.Stopped, + _ => DeviceStatus.Unknown + }; + } + + private async Task ValidateStateRulesAsync(int deviceId, DeviceState state) + { + var result = new DeviceValidationResult { IsValid = true, Issues = new List() }; + + switch (state) + { + case DeviceState.Running: + // Check if device should actually be running + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device != null && device.EnableProduction) + { + var status = await _collectionService.GetDeviceCurrentStatusAsync(deviceId); + if (status.Status != DeviceStatus.Running) + { + result.IsValid = false; + result.Issues.Add("Device is in Running state but actual status is not Running"); + } + } + break; + + case DeviceState.Error: + // Check if device has active errors + var activeAlarms = await _alarmService.GetActiveAlertsByDeviceAsync(deviceId); + if (!activeAlarms.Any(a => a.AlertType == "DeviceError")) + { + result.IsValid = false; + result.Issues.Add("Device is in Error state but has no active error alerts"); + } + break; + } + + return result; + } + + private async Task CheckStateTimeoutAsync(int deviceId, DeviceState state) + { + var stateContext = _deviceStates.GetValueOrDefault(deviceId); + if (stateContext == null) + return new StateTimeoutInfo { IsTimeout = false }; + + var stateDurations = GetStateTimeoutDurations(); + var maxDuration = stateDurations.GetValueOrDefault(state, TimeSpan.FromMinutes(30)); + + var currentDuration = DateTime.UtcNow - stateContext.StateChangedAt; + + return new StateTimeoutInfo + { + IsTimeout = currentDuration > maxDuration, + State = state, + CurrentDuration = currentDuration, + MaxDuration = maxDuration, + ExceededBy = currentDuration - maxDuration + }; + } + + private Dictionary GetStateTimeoutDurations() + { + return new Dictionary + { + [DeviceState.Unknown] = TimeSpan.FromMinutes(5), + [DeviceState.Offline] = TimeSpan.FromHours(1), + [DeviceState.Online] = TimeSpan.FromMinutes(15), + [DeviceState.Idle] = TimeSpan.FromMinutes(10), + [DeviceState.Running] = TimeSpan.FromHours(8), + [DeviceState.Error] = TimeSpan.FromMinutes(60), + [DeviceState.Maintenance] = TimeSpan.FromHours(4), + [DeviceState.Stopped] = TimeSpan.FromMinutes(30) + }; + } + + private async Task HandleInvalidStateAsync(int deviceId, DeviceValidationResult validationResult) + { + // Log warning + Console.WriteLine($"Device {deviceId} state validation failed: {string.Join(", ", validationResult.Issues)}"); + + // Attempt to transition to a safe state + var safeState = DetermineSafeState(deviceId, validationResult.CurrentState); + await ForceStateAsync(deviceId, safeState, $"State validation failed: {string.Join(", ", validationResult.Issues)}"); + } + + private async Task HandleStateTimeoutAsync(int deviceId, StateTimeoutInfo timeoutInfo) + { + // Log warning + Console.WriteLine($"Device {deviceId} state {timeoutInfo.State} exceeded maximum duration by {timeoutInfo.ExceededBy}"); + + // Trigger timeout event + await TriggerEventAsync(deviceId, DeviceEvent.Timeout, timeoutInfo); + } + + private DeviceState DetermineSafeState(int deviceId, DeviceState currentState) + { + return DeviceState.Idle; // Default safe state + } + + private async Task IsDeviceReadyForProduction(int deviceId) + { + // Check if device is ready for production + var device = await _deviceRepository.GetByIdAsync(deviceId); + var status = await _collectionService.GetDeviceCurrentStatusAsync(deviceId); + + return device != null && device.EnableProduction && + status.Status == DeviceStatus.Online && + !device.IsUnderMaintenance; + } + + private async Task IsProductionComplete(int deviceId) + { + // Check if production is complete + var records = await _deviceRepository.GetProductionRecordsByDeviceAsync(deviceId); + return records.Any() && records.Last().IsComplete; + } + + private async Task HasDeviceError(int deviceId) + { + // Check if device has errors + var alerts = await _alarmService.GetActiveAlertsByDeviceAsync(deviceId); + return alerts.Any(a => a.AlertType == "DeviceError"); + } + + private async Task IsMaintenanceComplete(int deviceId) + { + // Check if maintenance is complete + var device = await _deviceRepository.GetByIdAsync(deviceId); + return device != null && !device.IsUnderMaintenance; + } + + private string JsonSerializer(object obj) + { + // Simplified JSON serialization + return obj?.ToString() ?? "{}"; + } + + #endregion + } + + #region Supporting Classes and Interfaces + + public delegate Task StateChangeHandler(int deviceId, DeviceState fromState, DeviceState toState, DeviceStateTransitionContext context); + + public interface IStateAction + { + Task ExecuteAsync(int deviceId, DeviceStateTransitionContext context); + } + + public class StateActionResult + { + public bool Success { get; set; } + public string Message { get; set; } + } + + public class DeviceStateContext + { + public DeviceState CurrentState { get; set; } + public DeviceState PreviousState { get; set; } + public DateTime StateChangedAt { get; set; } + public object Context { get; set; } + } + + public class DeviceStateTransitionContext + { + public int DeviceId { get; set; } + public CNCDevice Device { get; set; } + public DeviceState FromState { get; set; } + public DeviceState ToState { get; set; } + public object Context { get; set; } + public DateTime Timestamp { get; set; } + } + + public class EventConfig + { + public DeviceState TargetState { get; set; } + public List>> Conditions { get; set; } + } + + public class StateTimeoutInfo + { + public bool IsTimeout { get; set; } + public DeviceState State { get; set; } + public TimeSpan CurrentDuration { get; set; } + public TimeSpan MaxDuration { get; set; } + public TimeSpan ExceededBy { get; set; } + } + + public class DeviceValidationResult + { + public int DeviceId { get; set; } + public bool IsValid { get; set; } + public List Issues { get; set; } + public DeviceState CurrentState { get; set; } + public DateTime Timestamp { get; set; } + } + + #endregion + + #region State Action Implementations + + public class StopProductionAction : IStateAction + { + public async Task ExecuteAsync(int deviceId, DeviceStateTransitionContext context) + { + // Implementation to stop production + return new StateActionResult { Success = true, Message = "Production stopped" }; + } + } + + public class StartProductionAction : IStateAction + { + public async Task ExecuteAsync(int deviceId, DeviceStateTransitionContext context) + { + // Implementation to start production + return new StateActionResult { Success = true, Message = "Production started" }; + } + } + + public class LogIdleAction : IStateAction + { + public async Task ExecuteAsync(int deviceId, DeviceStateTransitionContext context) + { + // Implementation to log idle state + return new StateActionResult { Success = true, Message = "Idle state logged" }; + } + } + + public class NotifyErrorAction : IStateAction + { + public async Task ExecuteAsync(int deviceId, DeviceStateTransitionContext context) + { + // Implementation to notify about error + return new StateActionResult { Success = true, Message = "Error notification sent" }; + } + } + + public class LogStateAction : IStateAction + { + private readonly string _actionType; + private readonly DeviceState _state; + + public LogStateAction(string actionType, DeviceState state) + { + _actionType = actionType; + _state = state; + } + + public async Task ExecuteAsync(int deviceId, DeviceStateTransitionContext context) + { + // Implementation to log state changes + return new StateActionResult { Success = true, Message = $"{_actionType} action for {_state} logged" }; + } + } + + #endregion +} \ No newline at end of file diff --git a/Haoliang.Core/Services/ICollectionServices.cs b/Haoliang.Core/Services/ICollectionServices.cs index c1968d3..78d5a23 100644 --- a/Haoliang.Core/Services/ICollectionServices.cs +++ b/Haoliang.Core/Services/ICollectionServices.cs @@ -49,9 +49,9 @@ namespace Haoliang.Core.Services public class PingResult { public bool IsSuccess { get; set; } - public int PingTime { get; set; } + public int PingTimeMs { get; set; } public string ErrorMessage { get; set; } - public DateTime PingTime { get; set; } + public DateTime Timestamp { get; set; } } public interface IRetryService diff --git a/Haoliang.Core/Services/IProductionStatisticsService.cs b/Haoliang.Core/Services/IProductionStatisticsService.cs new file mode 100644 index 0000000..0bf72e8 --- /dev/null +++ b/Haoliang.Core/Services/IProductionStatisticsService.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Haoliang.Models.Models.System; +using Haoliang.Models.Models.Production; + +namespace Haoliang.Core.Services +{ + public interface IProductionStatisticsService + { + /// + /// Calculate production trends for a specific device and time range + /// + Task CalculateProductionTrendsAsync(int deviceId, DateTime startDate, DateTime endDate); + + /// + /// Generate comprehensive production report + /// + Task GenerateProductionReportAsync(ReportFilter filter); + + /// + /// Calculate efficiency metrics for devices or programs + /// + Task CalculateEfficiencyMetricsAsync(EfficiencyFilter filter); + + /// + /// Perform quality analysis based on production data + /// + Task PerformQualityAnalysisAsync(QualityFilter filter); + + /// + /// Get production summary for dashboard display + /// + Task GetDashboardSummaryAsync(DashboardFilter filter); + + /// + /// Calculate OEE (Overall Equipment Effectiveness) + /// + Task CalculateOeeAsync(int deviceId, DateTime date); + + /// + /// Get production forecasts based on historical data + /// + Task GenerateProductionForecastAsync(ForecastFilter filter); + + /// + /// Analyze production anomalies and outliers + /// + Task DetectProductionAnomaliesAsync(AnomalyFilter filter); + } + + // Supporting models for statistics + public class ProductionTrendAnalysis + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + public decimal TotalProduction { get; set; } + public decimal AverageDailyProduction { get; set; } + public decimal ProductionVariance { get; set; } + public double TrendCoefficient { get; set; } + public ProductionTrendDirection TrendDirection { get; set; } + public List DailyData { get; set; } + } + + public class DailyProduction + { + public DateTime Date { get; set; } + public decimal Quantity { get; set; } + public decimal Target { get; set; } + public decimal Efficiency { get; set; } + public List Records { get; set; } + } + + public class ProductionReport + { + public DateTime ReportDate { get; set; } + public ReportType ReportType { get; set; } + public List SummaryItems { get; set; } + public ReportMetadata Metadata { get; set; } + } + + public class ProductionSummaryItem + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public string ProgramName { get; set; } + public decimal Quantity { get; set; } + public decimal TargetQuantity { get; set; } + public decimal Efficiency { get; set; } + public decimal QualityRate { get; set; } + public TimeSpan Runtime { get; set; } + public TimeSpan Downtime { get; set; } + } + + public class EfficiencyMetrics + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + public decimal Availability { get; set; } + public decimal Performance { get; set; } + public decimal Quality { get; set; } + public decimal Oee { get; set; } + public EquipmentUtilization Utilization { get; set; } + public List HourlyData { get; set; } + } + + public class HourlyEfficiency + { + public DateTime Hour { get; set; } + public decimal Availability { get; set; } + public decimal Performance { get; set; } + public decimal Quality { get; set; } + public decimal Oee { get; set; } + } + + public class QualityAnalysis + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + public decimal TotalProduced { get; set; } + public decimal TotalGood { get; set; } + public decimal QualityRate { get; set; } + public decimal DefectRate { get; set; } + public List QualityMetrics { get; set; } + public List DefectAnalysis { get; set; } + } + + public class QualityMetric + { + public string MetricName { get; set; } + public decimal Value { get; set; } + public string Unit { get; set; } + public DateTime Timestamp { get; set; } + } + + public class DefectAnalysis + { + public string DefectType { get; set; } + public int Count { get; set; } + public decimal Percentage { get; set; } + public List OccurrenceTimes { get; set; } + } + + public class DashboardSummary + { + public DateTime GeneratedAt { 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 DeviceSummaries { get; set; } + public List ActiveAlerts { get; set; } + } + + public class DeviceSummary + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DeviceStatus Status { get; set; } + public decimal TodayProduction { get; set; } + public decimal Efficiency { get; set; } + public decimal QualityRate { get; set; } + public TimeSpan Runtime { get; set; } + public string CurrentProgram { get; set; } + } + + public class AlertSummary + { + public int AlertId { get; set; } + public string DeviceName { get; set; } + public AlertType AlertType { get; set; } + public string Message { get; set; } + public DateTime CreatedAt { get; set; } + public bool IsActive { get; set; } + } + + public class OeeMetrics + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime Date { get; set; } + public decimal Availability { get; set; } + public decimal Performance { get; set; } + public decimal Quality { get; set; } + public decimal Oee { get; set; } + public TimeSpan PlannedProductionTime { get; set; } + public TimeSpan ActualProductionTime { get; set; } + public TimeSpan Downtime { get; set; } + public decimal IdealCycleTime { get; set; } + public decimal TotalCycleTime { get; set; } + public decimal TotalPieces { get; set; } + public decimal GoodPieces { get; set; } + } + + public class ProductionForecast + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime ForecastStartDate { get; set; } + public DateTime ForecastEndDate { get; set; } + public List DailyForecasts { get; set; } + public decimal ForecastAccuracy { get; set; } + public ForecastModel ModelUsed { get; set; } + } + + public class ForecastItem + { + public DateTime Date { get; set; } + public decimal ForecastedQuantity { get; set; } + public decimal ConfidenceLower { get; set; } + public decimal ConfidenceUpper { get; set; } + public decimal ActualQuantity { get; set; } + public decimal Variance { get; set; } + } + + public class AnomalyAnalysis + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime AnalysisStartDate { get; set; } + public DateTime AnalysisEndDate { get; set; } + public List Anomalies { get; set; } + public AnomalySeverity OverallSeverity { get; set; } + } + + public class ProductionAnomaly + { + public DateTime Timestamp { get; set; } + public AnomalyType Type { get; set; } + public AnomalySeverity Severity { get; set; } + public decimal Deviation { get; set; } + public string Description { get; set; } + public AnomalyAction RecommendedAction { get; set; } + } + + // Filter models + public class ReportFilter + { + public List DeviceIds { get; set; } + public List ProgramNames { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public ReportType ReportType { get; set; } + public GroupBy GroupBy { get; set; } + } + + public class EfficiencyFilter + { + public List DeviceIds { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public EfficiencyMetric Metrics { get; set; } + public GroupBy GroupBy { get; set; } + } + + public class QualityFilter + { + public List DeviceIds { get; set; } + public List ProgramNames { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public QualityMetricType MetricType { get; set; } + } + + public class DashboardFilter + { + public List DeviceIds { get; set; } + public DateTime Date { get; set; } + public bool IncludeAlerts { get; set; } = true; + } + + public class ForecastFilter + { + public int DeviceId { get; set; } + public int DaysToForecast { get; set; } + public ForecastModel Model { get; set; } + public DateTime HistoricalDataStart { get; set; } + public DateTime HistoricalDataEnd { get; set; } + } + + public class AnomalyFilter + { + public List DeviceIds { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public AnomalyType? Type { get; set; } + public AnomalySeverity? MinSeverity { get; set; } + } + + // Enum types + public enum ProductionTrendDirection + { + Increasing, + Decreasing, + Stable, + Volatile + } + + public enum ReportType + { + Daily, + Weekly, + Monthly, + Custom + } + + public enum ReportMetadata + { + GeneratedAt, + GeneratedBy, + Period, + DeviceCount, + TotalProduction, + AverageEfficiency + } + + public enum EquipmentUtilization + { + Low, + Medium, + High, + Optimal + } + + public enum AlertType + { + DeviceOffline, + ProductionAnomaly, + QualityIssue, + MaintenanceRequired, + SystemError + } + + public enum GroupBy + { + Device, + Program, + Date, + Hour + } + + public enum QualityMetricType + { + DefectRate, + FirstPassYield, + ReworkRate, + ScrapRate + } + + public enum ForecastModel + { + Linear, + ExponentialSmoothing, + Seasonal, + MovingAverage, + MachineLearning + } + + public enum AnomalyType + { + ProductionDrop, + QualitySpike, + DowntimeSpike, + EfficiencyDrop, + RuntimeAnomaly + } + + public enum AnomalySeverity + { + Low, + Medium, + High, + Critical + } + + public enum AnomalyAction + { + Monitor, + Investigate, + Alert, + Shutdown + } + + public enum EfficiencyMetric + { + Availability, + Performance, + Quality, + Oee, + All + } +} \ No newline at end of file diff --git a/Haoliang.Core/Services/MiddlewareServices.cs b/Haoliang.Core/Services/MiddlewareServices.cs index 97f1adc..7fd99c0 100644 --- a/Haoliang.Core/Services/MiddlewareServices.cs +++ b/Haoliang.Core/Services/MiddlewareServices.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; namespace Haoliang.Core.Services { @@ -113,29 +115,7 @@ namespace Haoliang.Core.Services public DateTime Timestamp { get; set; } public int Code { get; set; } - public static ApiResponse Success(object data = null, string message = "Success") - { - return new ApiResponse - { - Success = true, - Data = data, - Message = message, - Timestamp = DateTime.Now, - Code = 200 - }; - } - - public static ApiResponse Error(string message = "Error", int code = 500, object data = null) - { - return new ApiResponse - { - Success = false, - Data = data, - Message = message, - Timestamp = DateTime.Now, - Code = code - }; - } + public static ApiResponse NotFound(string message = "Resource not found") { diff --git a/Haoliang.Core/Services/ProductionStatisticsService.cs b/Haoliang.Core/Services/ProductionStatisticsService.cs new file mode 100644 index 0000000..2efb25a --- /dev/null +++ b/Haoliang.Core/Services/ProductionStatisticsService.cs @@ -0,0 +1,1094 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Haoliang.Core.Services; +using Haoliang.Data.Repositories; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Models.System; +using Haoliang.Models.Models.DataCollection; + +namespace Haoliang.Core.Services +{ + public class ProductionStatisticsService : IProductionStatisticsService + { + private readonly IProductionRepository _productionRepository; + private readonly IDeviceRepository _deviceRepository; + private readonly ISystemRepository _systemRepository; + private readonly IAlarmRepository _alarmRepository; + private readonly ICollectionRepository _collectionRepository; + + public ProductionStatisticsService( + IProductionRepository productionRepository, + IDeviceRepository deviceRepository, + ISystemRepository systemRepository, + IAlarmRepository alarmRepository, + ICollectionRepository collectionRepository) + { + _productionRepository = productionRepository; + _deviceRepository = deviceRepository; + _systemRepository = systemRepository; + _alarmRepository = alarmRepository; + _collectionRepository = collectionRepository; + } + + public async Task CalculateProductionTrendsAsync(int deviceId, DateTime startDate, DateTime endDate) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) + throw new KeyNotFoundException($"Device {deviceId} not found"); + + var productionRecords = await _productionRepository.GetProductionRecordsByDeviceAndDateRangeAsync( + deviceId, startDate, endDate); + + var dailyData = new List(); + decimal totalProduction = 0; + decimal sumSquaredDifferences = 0; + decimal mean = 0; + int dayCount = 0; + + // Group records by date + var groupedByDate = productionRecords.GroupBy(r => r.Created.Date); + + foreach (var dateGroup in groupedByDate) + { + var date = dateGroup.Key; + var dayRecords = dateGroup.ToList(); + var dayProduction = dayRecords.Sum(r => r.Quantity); + + // Get target for this date (if available) + var target = await GetProductionTargetAsync(deviceId, date); + + dailyData.Add(new DailyProduction + { + Date = date, + Quantity = dayProduction, + Target = target, + Efficiency = target > 0 ? (dayProduction / target) * 100 : 0, + Records = dayRecords + }); + + totalProduction += dayProduction; + dayCount++; + } + + if (dayCount > 0) + { + mean = totalProduction / dayCount; + + // Calculate variance + foreach (var day in dailyData) + { + sumSquaredDifferences += (decimal)Math.Pow((double)(day.Quantity - mean), 2); + } + } + + decimal productionVariance = dayCount > 0 ? (decimal)Math.Sqrt((double)(sumSquaredDifferences / dayCount)) : 0; + + // Calculate trend using linear regression + var trendCoefficient = CalculateLinearRegressionTrend(dailyData); + var trendDirection = DetermineTrendDirection(trendCoefficient); + + return new ProductionTrendAnalysis + { + DeviceId = deviceId, + DeviceName = device.Name, + PeriodStart = startDate, + PeriodEnd = endDate, + TotalProduction = totalProduction, + AverageDailyProduction = mean, + ProductionVariance = productionVariance, + TrendCoefficient = trendCoefficient, + TrendDirection = trendDirection, + DailyData = dailyData + }; + } + + public async Task GenerateProductionReportAsync(ReportFilter filter) + { + var summaryItems = new List(); + var metadata = new ReportMetadata(); + + // Get production records based on filter + var records = await _productionRepository.GetProductionRecordsByFilterAsync(filter); + + // Group by device and program + var groupedData = records.GroupBy(r => new { r.DeviceId, r.ProgramName }); + + foreach (var group in groupedData) + { + var deviceId = group.Key.DeviceId; + var programName = group.Key.ProgramName; + var device = await _deviceRepository.GetByIdAsync(deviceId); + + var totalQuantity = group.Sum(r => r.Quantity); + var totalTarget = group.Sum(r => r.TargetQuantity); + + // Calculate quality rate + var qualityRate = await CalculateQualityRateAsync(deviceId, programName, filter.StartDate, filter.EndDate); + + // Calculate runtime and downtime + var runtime = CalculateRuntime(group.ToList()); + var downtime = await CalculateDowntimeAsync(deviceId, filter.StartDate, filter.EndDate); + + summaryItems.Add(new ProductionSummaryItem + { + DeviceId = deviceId, + DeviceName = device?.Name ?? "Unknown", + ProgramName = programName, + Quantity = totalQuantity, + TargetQuantity = totalTarget, + Efficiency = totalTarget > 0 ? (totalQuantity / totalTarget) * 100 : 0, + QualityRate = qualityRate, + Runtime = runtime, + Downtime = downtime + }); + } + + metadata = CalculateReportMetadata(summaryItems, filter); + + return new ProductionReport + { + ReportDate = DateTime.Now, + ReportType = filter.ReportType, + SummaryItems = summaryItems, + Metadata = metadata + }; + } + + public async Task CalculateEfficiencyMetricsAsync(EfficiencyFilter filter) + { + var deviceIds = filter.DeviceIds?.Any() == true ? filter.DeviceIds : + (await _deviceRepository.GetAllActiveDevicesAsync()).Select(d => d.Id).ToList(); + + var metrics = new List(); + var hourlyData = new List(); + + foreach (var deviceId in deviceIds) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) continue; + + var efficiencyMetrics = await CalculateDeviceEfficiencyAsync(deviceId, filter); + metrics.Add(efficiencyMetrics); + + // Add hourly data if requested + if (filter.GroupBy == GroupBy.Hour) + { + hourlyData.AddRange(efficiencyMetrics.HourlyData); + } + } + + // Calculate aggregate metrics if multiple devices + var aggregatedMetrics = AggregateEfficiencyMetrics(metrics); + + return aggregatedMetrics; + } + + public async Task PerformQualityAnalysisAsync(QualityFilter filter) + { + var deviceIds = filter.DeviceIds?.Any() == true ? filter.DeviceIds : + (await _deviceRepository.GetAllActiveDevicesAsync()).Select(d => d.Id).ToList(); + + var totalProduced = 0m; + var totalGood = 0m; + var qualityMetrics = new List(); + var defectAnalysis = new List(); + + foreach (var deviceId in deviceIds) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) continue; + + // Get production records + var records = await _productionRepository.GetProductionRecordsByFilterAsync(new ReportFilter + { + DeviceIds = new List { deviceId }, + StartDate = filter.StartDate, + EndDate = filter.EndDate, + ProgramNames = filter.ProgramNames + }); + + var deviceTotalProduced = records.Sum(r => r.Quantity); + var deviceTotalGood = records.Where(r => r.IsGood).Sum(r => r.Quantity); + + totalProduced += deviceTotalProduced; + totalGood += deviceTotalGood; + + // Calculate quality metrics for this device + var deviceMetrics = await CalculateDeviceQualityMetricsAsync(deviceId, filter); + qualityMetrics.AddRange(deviceMetrics); + + // Perform defect analysis + var deviceDefects = await AnalyzeDefectsAsync(deviceId, filter); + defectAnalysis.AddRange(deviceDefects); + } + + var qualityRate = totalProduced > 0 ? (totalGood / totalProduced) * 100 : 0; + var defectRate = totalProduced > 0 ? 100 - qualityRate : 0; + + return new QualityAnalysis + { + DeviceIds = deviceIds, + DeviceNames = deviceIds.Select(id => _deviceRepository.GetByIdAsync(id).Result?.Name ?? "Unknown").ToList(), + PeriodStart = filter.StartDate, + PeriodEnd = filter.EndDate, + TotalProduced = totalProduced, + TotalGood = totalGood, + QualityRate = qualityRate, + DefectRate = defectRate, + QualityMetrics = qualityMetrics, + DefectAnalysis = defectAnalysis + }; + } + + public async Task GetDashboardSummaryAsync(DashboardFilter filter) + { + var deviceIds = filter.DeviceIds?.Any() == true ? filter.DeviceIds : + (await _deviceRepository.GetAllDevicesAsync()).Select(d => d.Id).ToList(); + + var deviceSummaries = new List(); + var activeAlerts = new List(); + + int totalDevices = deviceIds.Count; + int activeDevices = 0; + int offlineDevices = 0; + + decimal totalProductionToday = 0; + decimal totalProductionThisWeek = 0; + decimal totalProductionThisMonth = 0; + decimal overallEfficiency = 0; + decimal qualityRate = 0; + int efficiencyCount = 0; + + foreach (var deviceId in deviceIds) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) continue; + + // Get device status + var deviceStatus = await GetDeviceCurrentStatusAsync(deviceId); + + // Get today's production + var todayProduction = await GetDeviceProductionForDateAsync(deviceId, DateTime.Today); + + // Get weekly and monthly production + var weekStart = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek); + var weekProduction = await GetDeviceProductionForDateRangeAsync(deviceId, weekStart, DateTime.Today); + + var monthStart = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + var monthProduction = await GetDeviceProductionForDateRangeAsync(deviceId, monthStart, DateTime.Today); + + // Calculate efficiency and quality + var efficiency = await CalculateDeviceEfficiencyAsync(deviceId, new EfficiencyFilter + { + DeviceIds = new List { deviceId }, + StartDate = DateTime.Today, + EndDate = DateTime.Today + }); + + var quality = await CalculateQualityRateAsync(deviceId, null, DateTime.Today, DateTime.Today); + + deviceSummaries.Add(new DeviceSummary + { + DeviceId = deviceId, + DeviceName = device.Name, + Status = deviceStatus.Status, + TodayProduction = todayProduction, + Efficiency = efficiency.Oee, + QualityRate = quality, + Runtime = deviceStatus.Runtime, + CurrentProgram = deviceStatus.CurrentProgram + }); + + totalProductionToday += todayProduction; + totalProductionThisWeek += weekProduction; + totalProductionThisMonth += monthProduction; + overallEfficiency += efficiency.Oee; + qualityRate += quality; + efficiencyCount++; + + // Count devices by status + if (deviceStatus.Status == DeviceStatus.Online) + activeDevices++; + else + offlineDevices++; + + // Get active alerts + var alerts = await GetDeviceAlertsAsync(deviceId); + activeAlerts.AddRange(alerts); + } + + // Calculate averages + overallEfficiency = efficiencyCount > 0 ? overallEfficiency / efficiencyCount : 0; + qualityRate = efficiencyCount > 0 ? qualityRate / efficiencyCount : 0; + + return new DashboardSummary + { + GeneratedAt = DateTime.Now, + TotalDevices = totalDevices, + ActiveDevices = activeDevices, + OfflineDevices = offlineDevices, + TotalProductionToday = totalProductionToday, + TotalProductionThisWeek = totalProductionThisWeek, + TotalProductionThisMonth = totalProductionThisMonth, + OverallEfficiency = overallEfficiency, + QualityRate = qualityRate, + DeviceSummaries = deviceSummaries, + ActiveAlerts = activeAlerts + }; + } + + public async Task CalculateOeeAsync(int deviceId, DateTime date) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) + throw new KeyNotFoundException($"Device {deviceId} not found"); + + // Get planned production time (typically 8 hours or as configured) + var plannedProductionTime = await GetPlannedProductionTimeAsync(deviceId, date); + + // Get actual production time + var productionRecords = await _productionRepository.GetProductionRecordsByDeviceAndDateAsync(deviceId, date); + var actualProductionTime = CalculateProductionTime(productionRecords); + + // Calculate downtime + var downtime = plannedProductionTime - actualProductionTime; + + // Get performance metrics + var totalPieces = productionRecords.Sum(r => r.Quantity); + var goodPieces = productionRecords.Where(r => r.IsGood).Sum(r => r.Quantity); + + // Calculate OEE components + var availability = CalculateAvailability(plannedProductionTime, actualProductionTime); + var performance = await CalculatePerformanceAsync(deviceId, date); + var quality = totalPieces > 0 ? (goodPieces / totalPieces) * 100 : 0; + var oee = (availability * performance * quality) / 10000; // Convert percentage back to decimal + + return new OeeMetrics + { + DeviceId = deviceId, + DeviceName = device.Name, + Date = date, + Availability = availability, + Performance = performance, + Quality = quality, + Oee = oee, + PlannedProductionTime = plannedProductionTime, + ActualProductionTime = actualProductionTime, + Downtime = downtime, + IdealCycleTime = await GetIdealCycleTimeAsync(deviceId), + TotalCycleTime = actualProductionTime, + TotalPieces = totalPieces, + GoodPieces = goodPieces + }; + } + + public async Task GenerateProductionForecastAsync(ForecastFilter filter) + { + var device = await _deviceRepository.GetByIdAsync(filter.DeviceId); + if (device == null) + throw new KeyNotFoundException($"Device {filter.DeviceId} not found"); + + var historicalData = await _productionRepository.GetProductionRecordsByDeviceAndDateRangeAsync( + filter.DeviceId, filter.HistoricalDataStart, filter.HistoricalDataEnd); + + var dailyForecasts = new List(); + var forecastAccuracy = 0.0m; + + switch (filter.Model) + { + case ForecastModel.Linear: + dailyForecasts = GenerateLinearForecast(historicalData, filter.DaysToForecast); + break; + case ForecastModel.ExponentialSmoothing: + dailyForecasts = GenerateExponentialSmoothingForecast(historicalData, filter.DaysToForecast); + break; + case ForecastModel.MovingAverage: + dailyForecasts = GenerateMovingAverageForecast(historicalData, filter.DaysToForecast); + break; + default: + dailyForecasts = GenerateLinearForecast(historicalData, filter.DaysToForecast); + break; + } + + // Calculate forecast accuracy if we have actual data + forecastAccuracy = CalculateForecastAccuracy(dailyForecasts); + + return new ProductionForecast + { + DeviceId = filter.DeviceId, + DeviceName = device.Name, + ForecastStartDate = DateTime.Today, + ForecastEndDate = DateTime.Today.AddDays(filter.DaysToForecast - 1), + DailyForecasts = dailyForecasts, + ForecastAccuracy = forecastAccuracy, + ModelUsed = filter.Model + }; + } + + public async Task DetectProductionAnomaliesAsync(AnomalyFilter filter) + { + var deviceIds = filter.DeviceIds?.Any() == true ? filter.DeviceIds : + (await _deviceRepository.GetAllActiveDevicesAsync()).Select(d => d.Id).ToList(); + + var anomalies = new List(); + + foreach (var deviceId in deviceIds) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) continue; + + var productionRecords = await _productionRepository.GetProductionRecordsByDeviceAndDateRangeAsync( + deviceId, filter.StartDate, filter.EndDate); + + // Detect production drop anomalies + if (!filter.Type.HasValue || filter.Type.Value == AnomalyType.ProductionDrop) + { + var dropAnomalies = DetectProductionDropAnomalies(productionRecords); + anomalies.AddRange(dropAnomalies); + } + + // Detect quality spike anomalies + if (!filter.Type.HasValue || filter.Type.Value == AnomalyType.QualitySpike) + { + var qualityAnomalies = DetectQualitySpikeAnomalies(productionRecords); + anomalies.AddRange(qualityAnomalies); + } + + // Detect downtime spike anomalies + if (!filter.Type.HasValue || filter.Type.Value == AnomalyType.DowntimeSpike) + { + var downtimeAnomalies = DetectDowntimeSpikeAnomalies(deviceId, productionRecords); + anomalies.AddRange(downtimeAnomalies); + } + + // Detect efficiency drop anomalies + if (!filter.Type.HasValue || filter.Type.Value == AnomalyType.EfficiencyDrop) + { + var efficiencyAnomalies = DetectEfficiencyDropAnomalies(productionRecords); + anomalies.AddRange(efficiencyAnomalies); + } + } + + // Filter by severity + if (filter.MinSeverity.HasValue) + { + anomalies = anomalies.Where(a => a.Severity >= filter.MinSeverity.Value).ToList(); + } + + // Determine overall severity + var overallSeverity = DetermineOverallAnomalySeverity(anomalies); + + return new AnomalyAnalysis + { + DeviceIds = deviceIds, + DeviceNames = deviceIds.Select(id => _deviceRepository.GetByIdAsync(id).Result?.Name ?? "Unknown").ToList(), + AnalysisStartDate = filter.StartDate, + AnalysisEndDate = filter.EndDate, + Anomalies = anomalies, + OverallSeverity = overallSeverity + }; + } + + #region Private Methods + + private decimal CalculateLinearRegressionTrend(List dailyData) + { + if (dailyData.Count < 2) return 0; + + int n = dailyData.Count; + decimal sumX = 0, sumY = 0, sumXY = 0, sumXX = 0; + + for (int i = 0; i < n; i++) + { + sumX += i; + sumY += dailyData[i].Quantity; + sumXY += i * dailyData[i].Quantity; + sumXX += i * i; + } + + // Linear regression: y = mx + b, where m is the slope + decimal numerator = n * sumXY - sumX * sumY; + decimal denominator = n * sumXX - sumX * sumX; + + return denominator != 0 ? numerator / denominator : 0; + } + + private ProductionTrendDirection DetermineTrendDirection(decimal trendCoefficient) + { + if (trendCoefficient > 0.1m) + return ProductionTrendDirection.Increasing; + else if (trendCoefficient < -0.1m) + return ProductionTrendDirection.Decreasing; + else if (Math.Abs(trendCoefficient) <= 0.1m && Math.Abs(trendCoefficient) > 0.01m) + return ProductionTrendDirection.Stable; + else + return ProductionTrendDirection.Volatile; + } + + private async Task GetProductionTargetAsync(int deviceId, DateTime date) + { + // This would typically come from system configuration or production planning + var systemConfig = await _systemRepository.GetSystemConfigurationAsync(); + return systemConfig?.DailyProductionTarget ?? 100; // Default target + } + + private async Task CalculateQualityRateAsync(int deviceId, string programName, DateTime startDate, DateTime endDate) + { + var records = await _productionRepository.GetProductionRecordsByFilterAsync(new ReportFilter + { + DeviceIds = new List { deviceId }, + ProgramNames = programName != null ? new List { programName } : null, + StartDate = startDate, + EndDate = endDate + }); + + var totalQuantity = records.Sum(r => r.Quantity); + var goodQuantity = records.Where(r => r.IsGood).Sum(r => r.Quantity); + + return totalQuantity > 0 ? (goodQuantity / totalQuantity) * 100 : 0; + } + + private TimeSpan CalculateRuntime(List records) + { + if (records == null || records.Count == 0) return TimeSpan.Zero; + + var startTime = records.Min(r => r.Created); + var endTime = records.Max(r => r.Created); + return endTime - startTime; + } + + private async Task CalculateDowntimeAsync(int deviceId, DateTime startDate, DateTime endDate) + { + // Get device status changes + var statusRecords = await _collectionRepository.GetDeviceStatusHistoryAsync(deviceId, startDate, endDate); + + // Calculate total downtime (time when device was not in production state) + var downtime = TimeSpan.Zero; + var previousTime = startDate; + + foreach (var status in statusRecords.OrderBy(s => s.Timestamp)) + { + if (status.Status != DeviceStatus.Running && status.Status != DeviceStatus.Idle) + { + downtime += status.Timestamp - previousTime; + } + previousTime = status.Timestamp; + } + + // Add remaining time + downtime += endDate - previousTime; + + return downtime; + } + + private ReportMetadata CalculateReportMetadata(List summaryItems, ReportFilter filter) + { + return ReportMetadata.GeneratedAt; // Simplified for now + } + + private async Task CalculateDeviceEfficiencyAsync(int deviceId, EfficiencyFilter filter) + { + var device = await _deviceRepository.GetByIdAsync(deviceId); + if (device == null) + throw new KeyNotFoundException($"Device {deviceId} not found"); + + var records = await _productionRepository.GetProductionRecordsByDeviceAndDateRangeAsync( + deviceId, filter.StartDate, filter.EndDate); + + var hourlyData = new List(); + var totalAvailability = 0m; + var totalPerformance = 0m; + var totalQuality = 0m; + var hourCount = 0; + + // Group by hour if requested + if (filter.GroupBy == GroupBy.Hour) + { + var groupedByHour = records.GroupBy(r => r.Created.Date.AddHours(r.Created.Hour)); + + foreach (var hourGroup in groupedByHour) + { + var hour = hourGroup.Key; + var hourRecords = hourGroup.ToList(); + + // Calculate efficiency metrics for this hour + var availability = await CalculateHourlyAvailabilityAsync(deviceId, hour); + var performance = await CalculateHourlyPerformanceAsync(deviceId, hour); + var quality = CalculateHourlyQuality(hourRecords); + + hourlyData.Add(new HourlyEfficiency + { + Hour = hour, + Availability = availability, + Performance = performance, + Quality = quality, + Oee = (availability * performance * quality) / 100 + }); + + totalAvailability += availability; + totalPerformance += performance; + totalQuality += quality; + hourCount++; + } + } + + // Calculate overall metrics + var availability = hourCount > 0 ? totalAvailability / hourCount : 0; + var performance = hourCount > 0 ? totalPerformance / hourCount : 0; + var quality = hourCount > 0 ? totalQuality / hourCount : 0; + var oee = (availability * performance * quality) / 100; + + return new EfficiencyMetrics + { + DeviceId = deviceId, + DeviceName = device.Name, + PeriodStart = filter.StartDate, + PeriodEnd = filter.EndDate, + Availability = availability, + Performance = performance, + Quality = quality, + Oee = oee, + Utilization = DetermineUtilizationLevel(oee), + HourlyData = hourlyData + }; + } + + private EfficiencyMetrics AggregateEfficiencyMetrics(List metrics) + { + if (metrics == null || metrics.Count == 0) + return new EfficiencyMetrics(); + + var aggregated = new EfficiencyMetrics + { + DeviceIds = metrics.Select(m => m.DeviceId).ToList(), + PeriodStart = metrics.Min(m => m.PeriodStart), + PeriodEnd = metrics.Max(m => m.PeriodEnd), + Availability = metrics.Average(m => m.Availability), + Performance = metrics.Average(m => m.Performance), + Quality = metrics.Average(m => m.Quality), + Oee = metrics.Average(m => m.Oee), + HourlyData = metrics.SelectMany(m => m.HourlyData).ToList() + }; + + aggregated.Utilization = DetermineUtilizationLevel(aggregated.Oee); + return aggregated; + } + + private EquipmentUtilization DetermineUtilizationLevel(decimal oee) + { + if (oee >= 85) return EquipmentUtilization.Optimal; + if (oee >= 70) return EquipmentUtilization.High; + if (oee >= 50) return EquipmentUtilization.Medium; + return EquipmentUtilization.Low; + } + + private async Task> CalculateDeviceQualityMetricsAsync(int deviceId, QualityFilter filter) + { + // Simplified quality metrics calculation + var metrics = new List(); + + var defectRate = await CalculateQualityRateAsync(deviceId, null, filter.StartDate, filter.EndDate); + metrics.Add(new QualityMetric + { + MetricName = "Defect Rate", + Value = 100 - defectRate, + Unit = "%", + Timestamp = DateTime.Now + }); + + return metrics; + } + + private async Task> AnalyzeDefectsAsync(int deviceId, QualityFilter filter) + { + // Simplified defect analysis + return new List(); + } + + private async Task GetDeviceCurrentStatusAsync(int deviceId) + { + // This would typically get current status from real-time data + var records = await _collectionRepository.GetLatestDeviceStatusAsync(deviceId); + return records ?? new DeviceCurrentStatus(); + } + + private async Task GetDeviceProductionForDateAsync(int deviceId, DateTime date) + { + var records = await _productionRepository.GetProductionRecordsByDeviceAndDateAsync(deviceId, date); + return records.Sum(r => r.Quantity); + } + + private async Task GetDeviceProductionForDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate) + { + var records = await _productionRepository.GetProductionRecordsByDeviceAndDateRangeAsync(deviceId, startDate, endDate); + return records.Sum(r => r.Quantity); + } + + private async Task> GetDeviceAlertsAsync(int deviceId) + { + var alerts = await _alarmRepository.GetActiveAlertsByDeviceAsync(deviceId); + return alerts.Select(a => new AlertSummary + { + AlertId = a.Id, + DeviceName = "Device " + deviceId, // Simplified + AlertType = (AlertType)a.AlertType, + Message = a.Message, + CreatedAt = a.CreatedAt, + IsActive = a.IsActive + }).ToList(); + } + + private async Task GetPlannedProductionTimeAsync(int deviceId, DateTime date) + { + // This would typically come from system configuration + var systemConfig = await _systemRepository.GetSystemConfigurationAsync(); + return systemConfig?.DailyWorkingHours ?? TimeSpan.FromHours(8); + } + + private TimeSpan CalculateProductionTime(List records) + { + if (records == null || records.Count == 0) return TimeSpan.Zero; + + var startTime = records.Min(r => r.Created); + var endTime = records.Max(r => r.Created); + return endTime - startTime; + } + + private decimal CalculateAvailability(TimeSpan plannedTime, TimeSpan actualTime) + { + return plannedTime.TotalMinutes > 0 ? + (actualTime.TotalMinutes / plannedTime.TotalMinutes) * 100 : 0; + } + + private async Task CalculatePerformanceAsync(int deviceId, DateTime date) + { + // Simplified performance calculation + var records = await _productionRepository.GetProductionRecordsByDeviceAndDateAsync(deviceId, date); + var totalPieces = records.Sum(r => r.Quantity); + + var idealCycleTime = await GetIdealCycleTimeAsync(deviceId); + var standardPieces = idealCycleTime > 0 ? (24 * 60) / idealCycleTime : 0; // Assuming 24 hours operation + + return standardPieces > 0 ? (totalPieces / standardPieces) * 100 : 0; + } + + private async Task GetIdealCycleTimeAsync(int deviceId) + { + // This would typically come from device configuration + var device = await _deviceRepository.GetByIdAsync(deviceId); + return device?.IdealCycleTime ?? 1; // Default 1 minute per piece + } + + private decimal CalculateHourlyQuality(List hourRecords) + { + if (hourRecords == null || hourRecords.Count == 0) return 0; + + var totalQuantity = hourRecords.Sum(r => r.Quantity); + var goodQuantity = hourRecords.Where(r => r.IsGood).Sum(r => r.Quantity); + + return totalQuantity > 0 ? (goodQuantity / totalQuantity) * 100 : 0; + } + + private async Task CalculateHourlyAvailabilityAsync(int deviceId, DateTime hour) + { + // Simplified availability calculation + return 85m; // Default 85% availability + } + + private async Task CalculateHourlyPerformanceAsync(int deviceId, DateTime hour) + { + // Simplified performance calculation + return 90m; // Default 90% performance + } + + private List GenerateLinearForecast(List historicalData, int daysToForecast) + { + var forecasts = new List(); + var lastDate = historicalData.Max(r => r.Created); + + // Calculate average daily production + var avgDailyProduction = historicalData.Average(r => r.Quantity); + + for (int i = 1; i <= daysToForecast; i++) + { + var forecastDate = lastDate.AddDays(i); + var forecastQuantity = avgDailyProduction; // Simple average + + forecasts.Add(new ForecastItem + { + Date = forecastDate, + ForecastedQuantity = forecastQuantity, + ConfidenceLower = forecastQuantity * 0.8m, + ConfidenceUpper = forecastQuantity * 1.2m, + ActualQuantity = 0, // Will be updated when actual data is available + Variance = 0 + }); + } + + return forecasts; + } + + private List GenerateExponentialSmoothingForecast(List historicalData, int daysToForecast) + { + // Simplified exponential smoothing + var alpha = 0.3m; // Smoothing factor + var forecasts = new List(); + + if (historicalData.Count == 0) return forecasts; + + var lastSmoothed = historicalData.First().Quantity; + var lastDate = historicalData.Max(r => r.Created); + + for (int i = 1; i <= daysToForecast; i++) + { + var forecastDate = lastDate.AddDays(i); + var forecastQuantity = lastSmoothed; + + forecasts.Add(new ForecastItem + { + Date = forecastDate, + ForecastedQuantity = forecastQuantity, + ConfidenceLower = forecastQuantity * 0.85m, + ConfidenceUpper = forecastQuantity * 1.15m, + ActualQuantity = 0, + Variance = 0 + }); + + lastSmoothed = alpha * forecastQuantity + (1 - alpha) * lastSmoothed; + } + + return forecasts; + } + + private List GenerateMovingAverageForecast(List historicalData, int daysToForecast) + { + var forecasts = new List(); + var windowSize = Math.Min(7, historicalData.Count); // 7-day moving average + + if (historicalData.Count < windowSize) return forecasts; + + var lastDate = historicalData.Max(r => r.Created); + + // Calculate moving average + var movingAvg = historicalData.TakeLast(windowSize).Average(r => r.Quantity); + + for (int i = 1; i <= daysToForecast; i++) + { + var forecastDate = lastDate.AddDays(i); + var forecastQuantity = movingAvg; + + forecasts.Add(new ForecastItem + { + Date = forecastDate, + ForecastedQuantity = forecastQuantity, + ConfidenceLower = forecastQuantity * 0.9m, + ConfidenceUpper = forecastQuantity * 1.1m, + ActualQuantity = 0, + Variance = 0 + }); + } + + return forecasts; + } + + private decimal CalculateForecastAccuracy(List forecasts) + { + // Simplified accuracy calculation based on confidence intervals + var accurateForecasts = forecasts.Count(f => f.ForecastedQuantity >= f.ConfidenceLower && + f.ForecastedQuantity <= f.ConfidenceUpper); + return forecasts.Count > 0 ? (accurateForecasts / forecasts.Count) * 100 : 0; + } + + private List DetectProductionDropAnomalies(List records) + { + var anomalies = new List(); + + if (records.Count < 2) return anomalies; + + // Compare each day with the previous day + var groupedByDay = records.GroupBy(r => r.Created.Date).OrderBy(g => g.Key); + + var previousDay = groupedByDay.FirstOrDefault(); + foreach (var currentDay in groupedByDay.Skip(1)) + { + var previousQuantity = previousDay.Sum(r => r.Quantity); + var currentQuantity = currentDay.Sum(r => r.Quantity); + + if (previousQuantity > 0) + { + var dropPercentage = (decimal)((previousQuantity - currentQuantity) / (double)previousQuantity * 100); + + if (dropPercentage > 30) // 30% drop threshold + { + anomalies.Add(new ProductionAnomaly + { + Timestamp = currentDay.Key, + Type = AnomalyType.ProductionDrop, + Severity = dropPercentage > 50 ? AnomalySeverity.Critical : AnomalySeverity.High, + Deviation = dropPercentage, + Description = $"Production dropped by {dropPercentage:F1}% from previous day", + RecommendedAction = dropPercentage > 50 ? AnomalyAction.Shutdown : AnomalyAction.Investigate + }); + } + } + + previousDay = currentDay; + } + + return anomalies; + } + + private List DetectQualitySpikeAnomalies(List records) + { + var anomalies = new List(); + + if (records.Count == 0) return anomalies; + + var qualityRates = records.GroupBy(r => r.Created.Date) + .Select(g => new + { + Date = g.Key, + QualityRate = g.Where(r => r.IsGood).Average(r => (decimal)r.Quantity / (decimal)g.Sum(r => r.Quantity)) * 100 + }); + + var avgQuality = qualityRates.Average(q => q.QualityRate); + + foreach (var day in qualityRates) + { + var deviation = Math.Abs((decimal)(day.QualityRate - avgQuality)); + + if (deviation > 20 && day.QualityRate > avgQuality) // Significant quality improvement + { + anomalies.Add(new ProductionAnomaly + { + Timestamp = day.Date, + Type = AnomalyType.QualitySpike, + Severity = deviation > 30 ? AnomalySeverity.High : AnomalySeverity.Medium, + Deviation = deviation, + Description = $"Quality rate spiked to {day.QualityRate:F1}%", + RecommendedAction = AnomalyAction.Monitor + }); + } + } + + return anomalies; + } + + private List DetectDowntimeSpikeAnomalies(int deviceId, List records) + { + var anomalies = new List(); + + // Simplified downtime spike detection + var downtimePeriods = CalculateDowntimePeriods(records); + + var avgDowntime = downtimePeriods.Average(d => d.Duration.TotalMinutes); + + foreach (var period in downtimePeriods) + { + if (period.Duration.TotalMinutes > avgDowntime * 2) // Double the average downtime + { + anomalies.Add(new ProductionAnomaly + { + Timestamp = period.Start, + Type = AnomalyType.DowntimeSpike, + Severity = period.Duration.TotalMinutes > 120 ? AnomalySeverity.Critical : AnomalySeverity.High, + Deviation = (decimal)(period.Duration.TotalMinutes / avgDowntime), + Description = $"Extended downtime period: {period.Duration.TotalMinutes:F0} minutes", + RecommendedAction = period.Duration.TotalMinutes > 120 ? AnomalyAction.Shutdown : AnomalyAction.Alert + }); + } + } + + return anomalies; + } + + private List DetectEfficiencyDropAnomalies(List records) + { + var anomalies = new List(); + + // Simplified efficiency drop detection + var efficiencies = records.GroupBy(r => r.Created.Date) + .Select(g => new + { + Date = g.Key, + Efficiency = g.Sum(r => r.Quantity) / g.Sum(r => r.TargetQuantity) * 100 + }); + + var avgEfficiency = efficiencies.Average(e => e.Efficiency); + + foreach (var day in efficiencies) + { + if (day.Efficiency < avgEfficiency * 0.7) // 30% below average + { + anomalies.Add(new ProductionAnomaly + { + Timestamp = day.Date, + Type = AnomalyType.EfficiencyDrop, + Severity = day.Efficiency < avgEfficiency * 0.5 ? AnomalySeverity.Critical : AnomalySeverity.High, + Deviation = avgEfficiency - day.Efficiency, + Description = $"Efficiency dropped to {day.Efficiency:F1}%", + RecommendedAction = AnomalyAction.Investigate + }); + } + } + + return anomalies; + } + + private List CalculateDowntimePeriods(List records) + { + var downtimePeriods = new List(); + + if (records.Count == 0) return downtimePeriods; + + var sortedRecords = records.OrderBy(r => r.Created).ToList(); + var downtimeStart = sortedRecords.First().Created; + + foreach (var record in sortedRecords) + { + // Assume there's downtime if there's a gap of more than 5 minutes between records + if (record.Created - downtimeStart > TimeSpan.FromMinutes(5)) + { + downtimePeriods.Add(new DowntimePeriod + { + Start = downtimeStart, + End = record.Created, + Duration = record.Created - downtimeStart + }); + + downtimeStart = record.Created; + } + } + + return downtimePeriods; + } + + private AnomalySeverity DetermineOverallAnomalySeverity(List anomalies) + { + if (anomalies.Count == 0) return AnomalySeverity.Low; + + var maxSeverity = anomalies.Max(a => a.Severity); + var criticalCount = anomalies.Count(a => a.Severity == AnomalySeverity.Critical); + + if (criticalCount > 0) return AnomalySeverity.Critical; + if (maxSeverity >= AnomalySeverity.High) return AnomalySeverity.High; + if (maxSeverity >= AnomalySeverity.Medium) return AnomalySeverity.Medium; + + return AnomalySeverity.Low; + } + + #endregion + } + + // Helper class for downtime periods + internal class DowntimePeriod + { + public DateTime Start { get; set; } + public DateTime End { get; set; } + public TimeSpan Duration { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Core/Services/RealTimeService.cs b/Haoliang.Core/Services/RealTimeService.cs index 80c9eca..5a917be 100644 --- a/Haoliang.Core/Services/RealTimeService.cs +++ b/Haoliang.Core/Services/RealTimeService.cs @@ -2,470 +2,675 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using Haoliang.Models.Device; -using Haoliang.Models.System; -using Haoliang.Models.DataCollection; using Microsoft.AspNetCore.SignalR; +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 { public interface IRealTimeService { - Task ConnectClientAsync(string connectionId, string userId); + /// + /// Connect a client to WebSocket hub + /// + Task ConnectClientAsync(string connectionId, string userId, string clientType); + + /// + /// Disconnect a client + /// Task DisconnectClientAsync(string connectionId); - Task SubscribeToDevicesAsync(string connectionId, IEnumerable deviceIds); - Task UnsubscribeFromDevicesAsync(string connectionId, IEnumerable deviceIds); - Task SubscribeToAllDevicesAsync(string connectionId); - Task UnsubscribeFromAllDevicesAsync(string connectionId); - Task BroadcastDeviceStatusAsync(DeviceCurrentStatus status); - Task BroadcastProductionUpdateAsync(ProductionUpdate update); - Task BroadcastAlarmAsync(Alarm alarm); - Task BroadcastSystemMessageAsync(SystemMessage message); - Task SendToUserAsync(string userId, string method, object data); - Task SendToUsersAsync(IEnumerable userIds, string method, object data); - Task SendToAllAsync(string method, object data); - Task GetConnectedClientsCountAsync(); - Task> GetConnectedUsersAsync(); - Task IsUserConnectedAsync(string userId); - Task> GetUserSubscribedDevicesAsync(string userId); - Task StartHeartbeatAsync(); - Task StopHeartbeatAsync(); - } - public interface IWebSocketHub - { - Task OnConnectedAsync(string connectionId); - Task OnDisconnectedAsync(string connectionId); - Task OnSubscribeToDevicesAsync(string connectionId, IEnumerable deviceIds); - Task OnUnsubscribeFromDevicesAsync(string connectionId, IEnumerable deviceIds); - Task OnSubscribeToAlarmsAsync(string connectionId); - Task OnUnsubscribeFromAlarmsAsync(string connectionId); - Task OnRequestDeviceStatusAsync(string connectionId, int deviceId); - Task OnRequestProductionDataAsync(string connectionId, int deviceId); - Task OnRequestSystemStatsAsync(string connectionId); - } + /// + /// Join a device monitoring group + /// + Task JoinDeviceGroupAsync(string connectionId, int deviceId); + + /// + /// Leave a device monitoring group + /// + Task LeaveDeviceGroupAsync(string connectionId, int deviceId); + + /// + /// Join a dashboard group + /// + Task JoinDashboardGroupAsync(string connectionId, string dashboardId); + + /// + /// Leave a dashboard group + /// + Task LeaveDashboardGroupAsync(string connectionId, string dashboardId); + + /// + /// Broadcast device status update + /// + Task BroadcastDeviceStatusAsync(DeviceStatusUpdate statusUpdate); + + /// + /// Broadcast production update + /// + Task BroadcastProductionUpdateAsync(ProductionUpdate productionUpdate); + + /// + /// Broadcast alert update + /// + Task BroadcastAlertAsync(AlertUpdate alertUpdate); + + /// + /// Send system notification + /// + Task SendSystemNotificationAsync(SystemNotification notification); + + /// + /// Send real-time dashboard data + /// + Task SendDashboardUpdateAsync(DashboardUpdate dashboardUpdate); + + /// + /// Send command to specific client + /// + Task SendCommandToClientAsync(string connectionId, RealTimeCommand command); + + /// + /// Broadcast command to all clients + /// + Task BroadcastCommandAsync(RealTimeCommand command); + + /// + /// Get connected clients count + /// + Task GetConnectedClientsCountAsync(); - public interface IWebSocketAuthMiddleware - { - Task AuthenticateAsync(string connectionId, string token); - Task GetUserIdAsync(string connectionId); - Task GetConnectionIdAsync(string userId); - Task IsAuthenticatedAsync(string connectionId); - Task HasPermissionAsync(string connectionId, string permission); + /// + /// Get connected clients by type + /// + Task> GetConnectedClientsByTypeAsync(string clientType); + + /// + /// Get device monitoring status + /// + Task GetDeviceMonitoringStatusAsync(int deviceId); + + /// + /// Start data streaming for device + /// + Task StartDeviceStreamingAsync(int deviceId, int intervalMs = 1000); + + /// + /// Stop data streaming for device + /// + Task StopDeviceStreamingAsync(int deviceId); + + /// + /// Get active streaming devices + /// + Task> GetActiveStreamingDevicesAsync(); } - public class RealTimeManager : IRealTimeService + public class RealTimeService : IRealTimeService { private readonly IHubContext _hubContext; - private readonly IWebSocketAuthMiddleware _authMiddleware; - private readonly ICachingService _cachingService; - - // 用户连接信息 - private readonly ConcurrentDictionary _connectionUsers = new(); - // 用户订阅的设备 - private readonly ConcurrentDictionary> _userDeviceSubscriptions = new(); - // 用户订阅告警 - private readonly ConcurrentDictionary _userAlarmSubscriptions = new(); - - // 心跳定时器 - private System.Threading.Timer _heartbeatTimer; - private bool _isHeartbeatRunning = false; - - public RealTimeManager( + private readonly IDeviceCollectionService _deviceCollectionService; + private readonly IProductionService _productionService; + private readonly IAlarmService _alarmService; + private readonly ICacheService _cacheService; + private readonly ConcurrentDictionary _connectedClients = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _deviceStreaming = new ConcurrentDictionary(); + private readonly Timer _deviceStatusTimer; + private readonly Timer _productionTimer; + + public RealTimeService( IHubContext hubContext, - IWebSocketAuthMiddleware authMiddleware, - ICachingService cachingService) + IDeviceCollectionService deviceCollectionService, + IProductionService productionService, + IAlarmService alarmService, + ICacheService cacheService) { _hubContext = hubContext; - _authMiddleware = authMiddleware; - _cachingService = cachingService; + _deviceCollectionService = deviceCollectionService; + _productionService = productionService; + _alarmService = alarmService; + _cacheService = cacheService; + + // 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) + public async Task ConnectClientAsync(string connectionId, string userId, string clientType) { - _connectionUsers[connectionId] = userId; - await _cachingService.SetAsync($"user_connections_{userId}", new List { connectionId }, TimeSpan.FromMinutes(30)); - - LogDebug($"Client {connectionId} connected for user {userId}"); + var clientInfo = new ClientInfo + { + ConnectionId = connectionId, + UserId = userId, + ClientType = clientType, + ConnectedAt = DateTime.UtcNow, + LastActivity = DateTime.UtcNow, + Groups = new HashSet(), + DeviceGroups = new HashSet() + }; + + _connectedClients.AddOrUpdate(connectionId, clientInfo, (key, existing) => clientInfo); + + await _hubContext.Clients.Client(connectionId).SendAsync("ClientConnected", new + { + ClientId = connectionId, + UserId = userId, + ClientType = clientType, + Timestamp = DateTime.UtcNow + }); } public async Task DisconnectClientAsync(string connectionId) { - if (_connectionUsers.TryRemove(connectionId, out var userId)) + if (_connectedClients.TryRemove(connectionId, out var clientInfo)) { - // 更新用户连接列表 - var userConnections = await _cachingService.GetAsync>($"user_connections_{userId}"); - if (userConnections != null) + // Remove from all groups + foreach (var group in clientInfo.Groups) { - userConnections.Remove(connectionId); - if (userConnections.Count > 0) - { - await _cachingService.SetAsync($"user_connections_{userId}", userConnections, TimeSpan.FromMinutes(30)); - } - else - { - await _cachingService.RemoveAsync($"user_connections_{userId}"); - } + await _hubContext.Groups.RemoveFromGroupAsync(connectionId, group); } - // 清理设备订阅 - if (_userDeviceSubscriptions.TryRemove(connectionId, out var deviceIds)) + foreach (var deviceId in clientInfo.DeviceGroups) { - await UnsubscribeFromDevicesAsync(connectionId, deviceIds); + await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}"); } - // 清理告警订阅 - _userAlarmSubscriptions.TryRemove(connectionId, out _); - - LogDebug($"Client {connectionId} disconnected for user {userId}"); + // Notify other clients + await _hubContext.Clients.AllExcept(connectionId).SendAsync("ClientDisconnected", new + { + ClientId = connectionId, + UserId = clientInfo.UserId, + Timestamp = DateTime.UtcNow + }); } } - public async Task SubscribeToDevicesAsync(string connectionId, IEnumerable deviceIds) + public async Task JoinDeviceGroupAsync(string connectionId, int deviceId) { - if (!_connectionUsers.ContainsKey(connectionId)) + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) { - throw new InvalidOperationException("Connection not found"); - } + clientInfo.DeviceGroups.Add(deviceId); + clientInfo.LastActivity = DateTime.UtcNow; - var userId = _connectionUsers[connectionId]; - - // 获取或创建设备订阅集合 - if (!_userDeviceSubscriptions.TryGetValue(connectionId, out var subscriptions)) - { - subscriptions = new HashSet(); - _userDeviceSubscriptions[connectionId] = subscriptions; - } - - // 添加新订阅 - var newSubscriptions = deviceIds.Except(subscriptions).ToList(); - foreach (var deviceId in newSubscriptions) - { - subscriptions.Add(deviceId); - } + await _hubContext.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}"); - // 如果有新订阅,发送确认 - if (newSubscriptions.Count > 0) - { - await SendToUserAsync(userId, "DeviceSubscribed", new { DeviceIds = newSubscriptions }); - LogDebug($"User {userId} subscribed to devices: {string.Join(", ", newSubscriptions)}"); + // Send current device status + var deviceStatus = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId); + await _hubContext.Clients.Client(connectionId).SendAsync("DeviceStatusUpdated", new + { + DeviceId = deviceId, + Status = deviceStatus.Status, + Timestamp = DateTime.UtcNow + }); } } - public async Task UnsubscribeFromDevicesAsync(string connectionId, IEnumerable deviceIds) + public async Task LeaveDeviceGroupAsync(string connectionId, int deviceId) { - if (_userDeviceSubscriptions.TryGetValue(connectionId, out var subscriptions)) + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) { - var removedSubscriptions = deviceIds.Intersect(subscriptions).ToList(); - foreach (var deviceId in removedSubscriptions) - { - subscriptions.Remove(deviceId); - } - - // 如果有取消订阅,发送确认 - if (removedSubscriptions.Count > 0) - { - var userId = _connectionUsers[connectionId]; - await SendToUserAsync(userId, "DeviceUnsubscribed", new { DeviceIds = removedSubscriptions }); - LogDebug($"User {userId} unsubscribed from devices: {string.Join(", ", removedSubscriptions)}"); - } + clientInfo.DeviceGroups.Remove(deviceId); + await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}"); } } - public async Task SubscribeToAllDevicesAsync(string connectionId) + public async Task JoinDashboardGroupAsync(string connectionId, string dashboardId) { - if (!_connectionUsers.ContainsKey(connectionId)) + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) { - throw new InvalidOperationException("Connection not found"); - } + clientInfo.Groups.Add($"dashboard_{dashboardId}"); + clientInfo.LastActivity = DateTime.UtcNow; - var userId = _connectionUsers[connectionId]; - await _cachingService.SetAsync($"user_all_devices_{userId}", true, TimeSpan.FromMinutes(30)); - - await SendToUserAsync(userId, "SubscribedToAllDevices", null); - LogDebug($"User {userId} subscribed to all devices"); - } + await _hubContext.Groups.AddToGroupAsync(connectionId, $"dashboard_{dashboardId}"); - public async Task UnsubscribeFromAllDevicesAsync(string connectionId) - { - if (!_connectionUsers.ContainsKey(connectionId)) - { - throw new InvalidOperationException("Connection not found"); + // Send current dashboard data + var dashboardUpdate = await GetDashboardUpdateAsync(); + await _hubContext.Clients.Client(connectionId).SendAsync("DashboardUpdated", dashboardUpdate); } + } - var userId = _connectionUsers[connectionId]; - await _cachingService.RemoveAsync($"user_all_devices_{userId}"); - - // 清理设备订阅 - if (_userDeviceSubscriptions.TryGetValue(connectionId, out var deviceIds)) + public async Task LeaveDashboardGroupAsync(string connectionId, string dashboardId) + { + if (_connectedClients.TryGetValue(connectionId, out var clientInfo)) { - await UnsubscribeFromDevicesAsync(connectionId, deviceIds); + clientInfo.Groups.Remove($"dashboard_{dashboardId}"); + await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{dashboardId}"); } - - await SendToUserAsync(userId, "UnsubscribedFromAllDevices", null); - LogDebug($"User {userId} unsubscribed from all devices"); } - public async Task BroadcastDeviceStatusAsync(DeviceCurrentStatus status) + public async Task BroadcastDeviceStatusAsync(DeviceStatusUpdate statusUpdate) { - var message = new - { - DeviceId = status.DeviceId, - DeviceCode = status.DeviceCode, - Status = status.Status, - NCProgram = status.NCProgram, - CumulativeCount = status.CumulativeCount, - RecordTime = status.RecordTime, - IsActive = status.IsActive - }; - - // 发送给订阅了该设备的用户 - await SendToSubscribedUsersAsync($"Device_{status.DeviceId}", message); + await _hubContext.Clients.Group($"device_{statusUpdate.DeviceId}").SendAsync("DeviceStatusUpdated", statusUpdate); - // 如果有用户订阅了所有设备,也发送给他们 - await SendToAllDevicesSubscribersAsync("DeviceStatusUpdate", message); + // Also broadcast to dashboard groups + await _hubContext.Clients.Group("dashboard").SendAsync("DeviceStatusUpdated", statusUpdate); } - public async Task BroadcastProductionUpdateAsync(ProductionUpdate update) + public async Task BroadcastProductionUpdateAsync(ProductionUpdate productionUpdate) { - var message = new - { - DeviceId = update.DeviceId, - DeviceCode = update.DeviceCode, - NCProgram = update.NCProgram, - Quantity = update.Quantity, - Timestamp = update.Timestamp, - TotalCount = update.TotalCount - }; - - await SendToAllAsync("ProductionUpdate", message); + await _hubContext.Clients.Group($"device_{productionUpdate.DeviceId}").SendAsync("ProductionUpdated", productionUpdate); + + // Also broadcast to dashboard groups + await _hubContext.Clients.Group("dashboard").SendAsync("ProductionUpdated", productionUpdate); } - public async Task BroadcastAlarmAsync(Alarm alarm) + public async Task BroadcastAlertAsync(AlertUpdate alertUpdate) { - var message = new - { - AlarmId = alarm.AlarmId, - DeviceId = alarm.DeviceId, - DeviceCode = alarm.DeviceCode, - AlarmType = alarm.AlarmType, - Severity = alarm.Severity, - Title = alarm.Title, - Description = alarm.Description, - AlarmStatus = alarm.AlarmStatus, - CreateTime = alarm.CreateTime - }; - - // 发送给所有订阅告警的用户 - await SendToAlarmSubscribersAsync("AlarmCreated", message); + await _hubContext.Clients.Group("dashboard").SendAsync("AlertUpdated", alertUpdate); + await _hubContext.Clients.Group("alerts").SendAsync("AlertUpdated", alertUpdate); - // 发送给所有用户 - await SendToAllAsync("AlarmUpdate", message); + // 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); + } } - public async Task BroadcastSystemMessageAsync(SystemMessage message) + public async Task SendSystemNotificationAsync(SystemNotification notification) { - await SendToAllAsync("SystemMessage", message); + await _hubContext.Clients.Group("notifications").SendAsync("SystemNotification", notification); } - public async Task SendToUserAsync(string userId, string method, object data) + public async Task SendDashboardUpdateAsync(DashboardUpdate dashboardUpdate) { - var connections = await _cachingService.GetAsync>($"user_connections_{userId}"); - if (connections != null) - { - foreach (var connectionId in connections) - { - await _hubContext.Clients.Client(connectionId).SendAsync(method, data); - } - } + await _hubContext.Clients.Group("dashboard").SendAsync("DashboardUpdated", dashboardUpdate); } - public async Task SendToUsersAsync(IEnumerable userIds, string method, object data) + public async Task SendCommandToClientAsync(string connectionId, RealTimeCommand command) { - foreach (var userId in userIds) - { - await SendToUserAsync(userId, method, data); - } + await _hubContext.Clients.Client(connectionId).SendAsync("Command", command); } - public async Task SendToAllAsync(string method, object data) + public async Task BroadcastCommandAsync(RealTimeCommand command) { - await _hubContext.Clients.All.SendAsync(method, data); + await _hubContext.Clients.All.SendAsync("Command", command); } public async Task GetConnectedClientsCountAsync() { - return _connectionUsers.Count; - } + // 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); + } - public async Task> GetConnectedUsersAsync() - { - return _connectionUsers.Values.Distinct(); + return _connectedClients.Count; } - public async Task IsUserConnectedAsync(string userId) + public async Task> GetConnectedClientsByTypeAsync(string clientType) { - var connections = await _cachingService.GetAsync>($"user_connections_{userId}"); - return connections != null && connections.Count > 0; + return _connectedClients.Values + .Where(c => c.ClientType.Equals(clientType, StringComparison.OrdinalIgnoreCase)) + .ToList(); } - public async Task> GetUserSubscribedDevicesAsync(string userId) + public async Task GetDeviceMonitoringStatusAsync(int deviceId) { - var devices = new List(); - - // 获取直接订阅的设备 - foreach (var kvp in _userDeviceSubscriptions) - { - var userConnections = await _cachingService.GetAsync>($"user_connections_{userId}"); - if (userConnections != null && userConnections.Contains(kvp.Key)) - { - devices.AddRange(kvp.Value); - } - } + var streamingInfo = _deviceStreaming.GetValueOrDefault(deviceId); + var monitoringClients = _connectedClients.Values + .Count(c => c.DeviceGroups.Contains(deviceId)); - // 获取订阅所有设备的用户 - var allDevicesSubscribed = await _cachingService.GetAsync($"user_all_devices_{userId}"); - if (allDevicesSubscribed) + return new DeviceMonitoringStatus { - // 获取所有设备ID - devices.AddRange(await GetAllDeviceIdsAsync()); - } - - return devices.Distinct(); + DeviceId = deviceId, + IsStreaming = streamingInfo != null, + StreamingIntervalMs = streamingInfo?.IntervalMs ?? 0, + MonitoringClients = monitoringClients, + StreamingStartedAt = streamingInfo?.StartedAt, + LastStreamingUpdate = streamingInfo?.LastUpdate + }; } - public async Task StartHeartbeatAsync() + public async Task StartDeviceStreamingAsync(int deviceId, int intervalMs = 1000) { - if (_isHeartbeatRunning) + if (!_deviceStreaming.ContainsKey(deviceId)) { - return; - } + var streamingInfo = new DeviceStreamingInfo + { + DeviceId = deviceId, + IntervalMs = intervalMs, + StartedAt = DateTime.UtcNow, + LastUpdate = DateTime.UtcNow, + IsRunning = true + }; - _heartbeatTimer = new System.Threading.Timer( - async _ => await SendHeartbeatAsync(), - null, - TimeSpan.Zero, - TimeSpan.FromSeconds(30)); + _deviceStreaming.AddOrUpdate(deviceId, streamingInfo, (key, existing) => streamingInfo); - _isHeartbeatRunning = true; + // Start streaming task + Task.Run(() => StreamDeviceData(deviceId, intervalMs)); + } } - public async Task StopHeartbeatAsync() + public async Task StopDeviceStreamingAsync(int deviceId) { - if (_isHeartbeatRunning) + if (_deviceStreaming.TryRemove(deviceId, out var streamingInfo)) { - _heartbeatTimer?.Dispose(); - _heartbeatTimer = null; - _isHeartbeatRunning = false; + streamingInfo.IsRunning = false; } } - private async Task SendToSubscribedUsersAsync(string group, object message) + public async Task> GetActiveStreamingDevicesAsync() { - await _hubContext.Clients.Group(group).SendAsync("DeviceStatusUpdate", message); + return _deviceStreaming.Values + .Where(s => s.IsRunning) + .Select(s => s.DeviceId) + .ToList(); } - private async Task SendToAllDevicesSubscribersAsync(string method, object message) + #region Private Methods + + private void UpdateDeviceStatuses(object state) { - var userIds = (await GetConnectedUsersAsync()).ToList(); - await SendToUsersAsync(userIds, method, message); + Task.Run(async () => + { + try + { + var activeDevices = await _deviceCollectionService.GetAllActiveDevicesAsync(); + + foreach (var device in activeDevices) + { + var status = await _deviceCollectionService.GetDeviceCurrentStatusAsync(device.Id); + + 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); + } + } + catch (Exception ex) + { + // Log error + Console.WriteLine($"Error updating device statuses: {ex.Message}"); + } + }); } - private async Task SendToAlarmSubscribersAsync(string method, object message) + private void UpdateProductionData(object state) { - var alarmSubscribers = _userAlarmSubscriptions.Keys.ToList(); - await SendToUsersAsync(alarmSubscribers, method, message); + Task.Run(async () => + { + try + { + var date = DateTime.Today; + var devices = await _deviceCollectionService.GetAllActiveDevicesAsync(); + + foreach (var device in devices) + { + var production = await _productionService.GetDeviceProductionForDateAsync(device.Id, date); + + var productionUpdate = new ProductionUpdate + { + DeviceId = device.Id, + DeviceName = device.Name, + Quantity = production, + Timestamp = DateTime.UtcNow + }; + + await BroadcastProductionUpdateAsync(productionUpdate); + } + } + catch (Exception ex) + { + // Log error + Console.WriteLine($"Error updating production data: {ex.Message}"); + } + }); } - private async Task SendHeartbeatAsync() + private async Task StreamDeviceData(int deviceId, int intervalMs) { - var heartbeat = new + var streamingInfo = _deviceStreaming.GetValueOrDefault(deviceId); + if (streamingInfo == null || !streamingInfo.IsRunning) + return; + + try { - Timestamp = DateTime.Now, - ConnectedUsers = await GetConnectedClientsCountAsync(), - ActiveDevices = await GetActiveDeviceCountAsync() - }; + while (streamingInfo.IsRunning) + { + try + { + // Get current device status + var status = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId); + + // 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 SendToAllAsync("Heartbeat", heartbeat); + await Task.Delay(intervalMs); + } + } + catch (Exception ex) + { + Console.WriteLine($"Device streaming task for device {deviceId} failed: {ex.Message}"); + } } - private async Task GetActiveDeviceCountAsync() + private async Task GetDashboardUpdateAsync() { - // 这里需要从设备服务获取活跃设备数量 - // 暂时返回0,实际实现时需要注入设备服务 - return 0; - } + // Get dashboard summary from cache or service + var dashboardSummary = await _cacheService.GetOrSetDashboardSummaryAsync(DateTime.Today, + () => _productionService.GetDashboardSummaryAsync(new DashboardFilter { Date = DateTime.Today })); - private async Task> GetAllDeviceIdsAsync() - { - // 这里需要从设备服务获取所有设备ID - // 暂时返回空集合,实际实现时需要注入设备服务 - return Enumerable.Empty(); + return new DashboardUpdate + { + Timestamp = DateTime.UtcNow, + TotalDevices = dashboardSummary.TotalDevices, + ActiveDevices = dashboardSummary.ActiveDevices, + OfflineDevices = dashboardSummary.OfflineDevices, + TotalProductionToday = dashboardSummary.TotalProductionToday, + TotalProductionThisWeek = dashboardSummary.TotalProductionThisWeek, + TotalProductionThisMonth = dashboardSummary.TotalProductionThisMonth, + OverallEfficiency = dashboardSummary.OverallEfficiency, + QualityRate = dashboardSummary.QualityRate, + DeviceSummaries = dashboardSummary.DeviceSummaries + }; } - private void LogDebug(string message) - { - // 这里应该注入日志服务 - Console.WriteLine($"[RealTimeManager] {message}"); - } + #endregion } - public class RealTimeHub : Hub + #region Supporting Models + + public class RealTimeHub : Hub { private readonly IRealTimeService _realTimeService; - private readonly IWebSocketAuthMiddleware _authMiddleware; - public RealTimeHub( - IRealTimeService realTimeService, - IWebSocketAuthMiddleware authMiddleware) + public RealTimeHub(IRealTimeService realTimeService) { _realTimeService = realTimeService; - _authMiddleware = authMiddleware; } public override async Task OnConnectedAsync() { - var connectionId = Context.ConnectionId; - - // 获取用户token(从查询参数或头信息) - var token = Context.GetHttpContext()?.Request.Query["token"]; - if (!string.IsNullOrEmpty(token)) - { - await _authMiddleware.AuthenticateAsync(connectionId, token); - } - await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { - await _realTimeService.DisconnectClientAsync(Context.ConnectionId); await base.OnDisconnectedAsync(exception); } - public async Task SubscribeToDevicesAsync(IEnumerable deviceIds) + public async Task JoinDeviceGroup(int deviceId) { - await _realTimeService.SubscribeToDevicesAsync(Context.ConnectionId, deviceIds); - await Clients.Caller.OnSubscribeToDevicesComplete(); + await _realTimeService.JoinDeviceGroupAsync(Context.ConnectionId, deviceId); + await Clients.Caller.SendAsync("JoinedDeviceGroup", new { DeviceId = deviceId }); } - public async Task UnsubscribeFromDevicesAsync(IEnumerable deviceIds) + public async Task LeaveDeviceGroup(int deviceId) { - await _realTimeService.UnsubscribeFromDevicesAsync(Context.ConnectionId, deviceIds); - await Clients.Caller.OnUnsubscribeFromDevicesComplete(); + await _realTimeService.LeaveDeviceGroupAsync(Context.ConnectionId, deviceId); + await Clients.Caller.SendAsync("LeftDeviceGroup", new { DeviceId = deviceId }); } - public async Task SubscribeToAlarmsAsync() + public async Task JoinDashboardGroup(string dashboardId) { - // 实现订阅告警的逻辑 - await Clients.Caller.OnSubscribeToAlarmsComplete(); + await _realTimeService.JoinDashboardGroupAsync(Context.ConnectionId, dashboardId); + await Clients.Caller.SendAsync("JoinedDashboardGroup", new { DashboardId = dashboardId }); } - public async Task UnsubscribeFromAlarmsAsync() + public async Task LeaveDashboardGroup(string dashboardId) { - // 实现取消订阅告警的逻辑 - await Clients.Caller.OnUnsubscribeFromAlarmsComplete(); + await _realTimeService.LeaveDashboardGroupAsync(Context.ConnectionId, dashboardId); + await Clients.Caller.SendAsync("LeftDashboardGroup", new { DashboardId = dashboardId }); } + + public async Task RequestDeviceStreaming(int deviceId, int intervalMs = 1000) + { + await _realTimeService.StartDeviceStreamingAsync(deviceId, intervalMs); + await Clients.Caller.SendAsync("DeviceStreamingStarted", new { DeviceId = deviceId, IntervalMs = intervalMs }); + } + + public async Task StopDeviceStreaming(int deviceId) + { + await _realTimeService.StopDeviceStreamingAsync(deviceId); + await Clients.Caller.SendAsync("DeviceStreamingStopped", new { DeviceId = deviceId }); + } + + public async Task Ping() + { + await Clients.Caller.SendAsync("Pong", new { Timestamp = DateTime.UtcNow }); + } + } + + 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 Groups { get; set; } + public HashSet 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 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 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 } \ No newline at end of file diff --git a/Haoliang.Core/Services/RulesService.cs b/Haoliang.Core/Services/RulesService.cs new file mode 100644 index 0000000..49ee57e --- /dev/null +++ b/Haoliang.Core/Services/RulesService.cs @@ -0,0 +1,438 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Haoliang.Models.Models.System; + +namespace Haoliang.Core.Services +{ + public interface IRulesService + { + /// + /// Get all business rules + /// + Task> GetAllRulesAsync(); + + /// + /// Create or update business rule + /// + Task CreateOrUpdateRuleAsync(BusinessRuleConfig rule); + + /// + /// Delete business rule + /// + Task DeleteRuleAsync(int ruleId); + + /// + /// Get statistics rules + /// + Task> GetStatisticsRulesAsync(); + + /// + /// Update statistics rules + /// + Task UpdateStatisticsRulesAsync(List rules); + + /// + /// Validate business rule expression + /// + Task ValidateRuleAsync(BusinessRuleConfig rule); + + /// + /// Evaluate business rule against data + /// + Task EvaluateRuleAsync(BusinessRuleConfig rule, object data); + + /// + /// Get rule execution history + /// + Task> GetRuleExecutionHistoryAsync(int ruleId, DateTime? startDate = null, DateTime? endDate = null); + } + + public class RulesService : IRulesService + { + private readonly ISystemRepository _systemRepository; + private readonly IProductionRepository _productionRepository; + private readonly IAlarmRepository _alarmRepository; + private readonly ICacheService _cacheService; + + public RulesService( + ISystemRepository systemRepository, + IProductionRepository productionRepository, + IAlarmRepository alarmRepository, + ICacheService cacheService) + { + _systemRepository = systemRepository; + _productionRepository = productionRepository; + _alarmRepository = alarmRepository; + _cacheService = cacheService; + } + + public async Task> GetAllRulesAsync() + { + return await _cacheService.GetOrSetAllRulesAsync(() => + _systemRepository.GetAllBusinessRulesAsync()); + } + + public async Task CreateOrUpdateRuleAsync(BusinessRuleConfig rule) + { + // Validate rule before saving + var validationResult = await ValidateRuleAsync(rule); + if (!validationResult.IsValid) + { + throw new ArgumentException($"Invalid rule: {validationResult.ErrorMessage}"); + } + + // Save rule to repository + var savedRule = await _systemRepository.SaveBusinessRuleAsync(rule); + + // Clear cache + _cacheService.InvalidateRulesCache(); + + return savedRule; + } + + public async Task DeleteRuleAsync(int ruleId) + { + var result = await _systemRepository.DeleteBusinessRuleAsync(ruleId); + + if (result) + { + _cacheService.InvalidateRulesCache(); + } + + return result; + } + + public async Task> GetStatisticsRulesAsync() + { + return await _cacheService.GetOrSetAllStatisticsRulesAsync(() => + _systemRepository.GetAllStatisticsRulesAsync()); + } + + public async Task UpdateStatisticsRulesAsync(List rules) + { + // Validate all rules + foreach (var rule in rules) + { + var validationResult = await ValidateStatisticsRuleAsync(rule); + if (!validationResult.IsValid) + { + throw new ArgumentException($"Invalid statistics rule: {validationResult.ErrorMessage}"); + } + } + + // Save all rules + var result = await _systemRepository.SaveStatisticsRulesAsync(rules); + + if (result) + { + _cacheService.InvalidateRulesCache(); + } + + return result; + } + + public async Task ValidateRuleAsync(BusinessRuleConfig rule) + { + var result = new RuleValidationResult { IsValid = true }; + + if (rule == null) + { + result.IsValid = false; + result.ErrorMessage = "Rule cannot be null"; + return result; + } + + if (string.IsNullOrWhiteSpace(rule.RuleName)) + { + result.IsValid = false; + result.ErrorMessage = "Rule name cannot be empty"; + return result; + } + + if (string.IsNullOrWhiteSpace(rule.RuleExpression)) + { + result.IsValid = false; + result.ErrorMessage = "Rule expression cannot be empty"; + return result; + } + + // Validate rule syntax + if (!IsValidRuleExpression(rule.RuleExpression)) + { + result.IsValid = false; + result.ErrorMessage = "Invalid rule expression syntax"; + return result; + } + + // Test rule with sample data + try + { + var sampleData = GetSampleDataForRule(rule); + var testResult = await EvaluateRuleAsync(rule, sampleData); + + if (!testResult.Success) + { + result.IsValid = false; + result.ErrorMessage = $"Rule evaluation failed: {testResult.ErrorMessage}"; + } + } + catch (Exception ex) + { + result.IsValid = false; + result.ErrorMessage = $"Rule validation error: {ex.Message}"; + } + + return result; + } + + public async Task EvaluateRuleAsync(BusinessRuleConfig rule, object data) + { + var result = new RuleEvaluationResult { Success = true }; + + try + { + // Parse and evaluate the rule expression + var evaluator = new RuleExpressionEvaluator(); + var evaluationResult = evaluator.Evaluate(rule.RuleExpression, data); + + result.Success = evaluationResult.Success; + result.Result = evaluationResult.Result; + result.EvaluationTime = evaluationResult.EvaluationTime; + result.ErrorMessage = evaluationResult.ErrorMessage; + + // Log rule execution if successful + if (result.Success && rule.Enabled) + { + await LogRuleExecutionAsync(rule, data, result); + } + } + catch (Exception ex) + { + result.Success = false; + result.ErrorMessage = ex.Message; + } + + return result; + } + + public async Task> GetRuleExecutionHistoryAsync(int ruleId, DateTime? startDate = null, DateTime? endDate = null) + { + return await _systemRepository.GetRuleExecutionHistoryAsync(ruleId, startDate, endDate); + } + + #region Private Methods + + private bool IsValidRuleExpression(string expression) + { + // Basic validation - in a real implementation, you would use a proper expression parser + return !string.IsNullOrWhiteSpace(expression) && + !expression.Contains("DELETE") && + !expression.Contains("DROP") && + !expression.Contains("TRUNCATE"); + } + + private object GetSampleDataForRule(BusinessRuleConfig rule) + { + // Return sample data based on rule type + return new + { + Production = new { Quantity = 100, Target = 120, Quality = 95 }, + Device = new { Status = "Running", Efficiency = 85 }, + Time = DateTime.Now + }; + } + + private async Task ValidateStatisticsRuleAsync(StatisticsRuleConfig rule) + { + // Implementation for validating statistics rules + if (string.IsNullOrWhiteSpace(rule.RuleName)) + { + throw new ArgumentException("Statistics rule name cannot be empty"); + } + + if (string.IsNullOrWhiteSpace(rule.CalculationExpression)) + { + throw new ArgumentException("Statistics rule calculation expression cannot be empty"); + } + } + + private async Task LogRuleExecutionAsync(BusinessRuleConfig rule, object data, RuleEvaluationResult result) + { + var execution = new RuleExecutionHistory + { + RuleId = rule.RuleId, + RuleName = rule.RuleName, + InputDataJson = System.Text.Json.JsonSerializer.Serialize(data), + Result = result.Result?.ToString(), + Success = result.Success, + ErrorMessage = result.ErrorMessage, + ExecutionTime = DateTime.UtcNow + }; + + await _systemRepository.LogRuleExecutionAsync(execution); + } + + #endregion + } + + #region Supporting Classes + + public class RuleExpressionEvaluator + { + public RuleEvaluationResult Evaluate(string expression, object data) + { + var result = new RuleEvaluationResult(); + var startTime = DateTime.UtcNow; + + try + { + // Parse the expression and evaluate against data + // This is a simplified implementation + // In a real scenario, you would use a proper expression parser or scripting engine + + var parser = new ExpressionParser(); + var evaluationResult = parser.ParseAndEvaluate(expression, data); + + result.Success = evaluationResult.Success; + result.Result = evaluationResult.Value; + result.ErrorMessage = evaluationResult.Error; + } + catch (Exception ex) + { + result.Success = false; + result.ErrorMessage = ex.Message; + } + + result.EvaluationTime = DateTime.UtcNow - startTime; + return result; + } + } + + public class ExpressionParser + { + public ParseResult ParseAndEvaluate(string expression, object data) + { + var result = new ParseResult(); + + try + { + // Simple expression evaluation + // In a real implementation, you would use a proper expression parser + // like NCalc, System.Linq.Dynamic.Core, or a custom parser + + if (expression.Contains(">")) + { + var parts = expression.Split('>'); + if (parts.Length == 2) + { + var left = EvaluateExpression(parts[0].Trim(), data); + var right = EvaluateExpression(parts[1].Trim(), data); + + if (left != null && right != null) + { + result.Value = Convert.ToDecimal(left) > Convert.ToDecimal(right); + } + } + } + else if (expression.Contains("<")) + { + var parts = expression.Split('<'); + if (parts.Length == 2) + { + var left = EvaluateExpression(parts[0].Trim(), data); + var right = EvaluateExpression(parts[1].Trim(), data); + + if (left != null && right != null) + { + result.Value = Convert.ToDecimal(left) < Convert.ToDecimal(right); + } + } + } + else if (expression.Contains("=")) + { + var parts = expression.Split('='); + if (parts.Length == 2) + { + var left = EvaluateExpression(parts[0].Trim(), data); + var right = EvaluateExpression(parts[1].Trim(), data); + + if (left != null && right != null) + { + result.Value = left.ToString() == right.ToString(); + } + } + } + else + { + // Simple value evaluation + result.Value = EvaluateExpression(expression, data); + } + + result.Success = true; + } + catch (Exception ex) + { + result.Success = false; + result.Error = ex.Message; + } + + return result; + } + + private object EvaluateExpression(string expression, object data) + { + // Simple property extraction from data object + // In a real implementation, this would be more sophisticated + + if (data is null) + return null; + + // Handle simple property access + if (expression.Contains(".")) + { + var parts = expression.Split('.'); + if (parts.Length == 2) + { + var property = parts[1]; + var dataDict = System.Text.Json.JsonSerializer.Deserialize>( + System.Text.Json.JsonSerializer.Serialize(data)); + + return dataDict?.TryGetValue(property, out var value) == true ? value : null; + } + } + + // Handle simple numeric comparison + if (decimal.TryParse(expression, out var numericValue)) + { + return numericValue; + } + + return null; + } + } + + public class ParseResult + { + public bool Success { get; set; } + public object Value { get; set; } + public string Error { get; set; } + } + + public class RuleEvaluationResult + { + public bool Success { get; set; } + public object Result { get; set; } + public TimeSpan EvaluationTime { get; set; } + public string ErrorMessage { get; set; } + } + + public class RuleValidationResult + { + public bool IsValid { get; set; } + public string ErrorMessage { get; set; } + public List Warnings { get; set; } = new List(); + } + + #endregion +} \ No newline at end of file diff --git a/Haoliang.Core/Services/TagMappingService.cs b/Haoliang.Core/Services/TagMappingService.cs new file mode 100644 index 0000000..6c42f9b --- /dev/null +++ b/Haoliang.Core/Services/TagMappingService.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Haoliang.Models.Template; +using Haoliang.Models.Device; +using Haoliang.Models.DataCollection; +using Haoliang.Data.Repositories; + +namespace Haoliang.Core.Services +{ + public interface ITagMappingService + { + Task CreateTagMappingAsync(TagMapping mapping); + Task UpdateTagMappingAsync(int mappingId, TagMapping mapping); + Task DeleteTagMappingAsync(int mappingId); + Task GetTagMappingByIdAsync(int mappingId); + Task> GetAllTagMappingsAsync(); + Task> GetMappingsByTemplateAsync(int templateId); + Task MapDeviceTagAsync(TagData deviceTag, int templateId); + Task> MapDeviceTagsAsync(IEnumerable deviceTags, int templateId); + Task ValidateTagMappingAsync(TagMapping mapping); + Task> GetMappingsByTagIdAsync(string tagId); + Task IsTagMappedAsync(string tagId, int templateId); + Task ValidateAndMapTagsAsync(IEnumerable deviceTags, int templateId); + } + + public class TagMappingService : ITagMappingService + { + private readonly ITagMappingRepository _tagMappingRepository; + private readonly ITemplateRepository _templateRepository; + private readonly ILoggingService _loggingService; + + public TagMappingService( + ITagMappingRepository tagMappingRepository, + ITemplateRepository templateRepository, + ILoggingService loggingService) + { + _tagMappingRepository = tagMappingRepository; + _templateRepository = templateRepository; + _loggingService = loggingService; + } + + public async Task CreateTagMappingAsync(TagMapping mapping) + { + // Validate mapping + await ValidateTagMappingAsync(mapping); + + // Check if mapping already exists + if (await IsTagMappedAsync(mapping.DeviceTagId, mapping.TemplateId)) + { + throw new InvalidOperationException($"Tag {mapping.DeviceTagId} is already mapped for template {mapping.TemplateId}"); + } + + mapping.MappingId = 0; // Ensure new mapping + mapping.CreatedAt = DateTime.Now; + mapping.UpdatedAt = DateTime.Now; + + await _tagMappingRepository.AddAsync(mapping); + await _tagMappingRepository.SaveAsync(); + + await _loggingService.LogInfoAsync($"Created tag mapping: {mapping.DeviceTagId} -> {mapping.SystemTagId} for template {mapping.TemplateId}"); + return mapping; + } + + public async Task UpdateTagMappingAsync(int mappingId, TagMapping mapping) + { + var existingMapping = await _tagMappingRepository.GetByIdAsync(mappingId); + if (existingMapping == null) + throw new KeyNotFoundException($"Tag mapping with ID {mappingId} not found"); + + // Validate updated mapping + await ValidateTagMappingAsync(mapping); + + existingMapping.DeviceTagId = mapping.DeviceTagId; + existingMapping.SystemTagId = mapping.SystemTagId; + existingMapping.DataType = mapping.DataType; + existingMapping.ConversionFormula = mapping.ConversionFormula; + existingMapping.Description = mapping.Description; + existingMapping.IsActive = mapping.IsActive; + existingMapping.UpdatedAt = DateTime.Now; + + await _tagMappingRepository.UpdateAsync(existingMapping); + await _tagMappingRepository.SaveAsync(); + + await _loggingService.LogInfoAsync($"Updated tag mapping: {existingMapping.DeviceTagId} -> {existingMapping.SystemTagId}"); + return existingMapping; + } + + public async Task DeleteTagMappingAsync(int mappingId) + { + var mapping = await _tagMappingRepository.GetByIdAsync(mappingId); + if (mapping == null) + return false; + + await _tagMappingRepository.DeleteAsync(mapping); + await _tagMappingRepository.SaveAsync(); + + await _loggingService.LogInfoAsync($"Deleted tag mapping: {mapping.DeviceTagId} -> {mapping.SystemTagId}"); + return true; + } + + public async Task GetTagMappingByIdAsync(int mappingId) + { + return await _tagMappingRepository.GetByIdAsync(mappingId); + } + + public async Task> GetAllTagMappingsAsync() + { + return await _tagMappingRepository.GetAllAsync(); + } + + public async Task> GetMappingsByTemplateAsync(int templateId) + { + return await _tagMappingRepository.GetMappingsByTemplateAsync(templateId); + } + + public async Task MapDeviceTagAsync(TagData deviceTag, int templateId) + { + if (deviceTag == null) + throw new ArgumentNullException(nameof(deviceTag)); + + var template = await _templateRepository.GetByIdAsync(templateId); + if (template == null) + throw new KeyNotFoundException($"Template with ID {templateId} not found"); + + // Find mapping for this device tag + var mapping = await _tagMappingRepository.GetByDeviceTagAndTemplateAsync(deviceTag.Id, templateId); + if (mapping == null) + { + await _loggingService.LogWarningAsync($"No mapping found for tag {deviceTag.Id} in template {templateId}"); + return null; + } + + // Apply mapping and conversion + var mappedTag = ApplyTagMapping(deviceTag, mapping); + return mapping; + } + + public async Task> MapDeviceTagsAsync(IEnumerable deviceTags, int templateId) + { + if (deviceTags == null) + throw new ArgumentNullException(nameof(deviceTags)); + + var template = await _templateRepository.GetByIdAsync(templateId); + if (template == null) + throw new KeyNotFoundException($"Template with ID {templateId} not found"); + + var mappings = await GetMappingsByTemplateAsync(templateId); + var mappingDict = mappings.ToDictionary(m => m.DeviceTagId); + + var result = new Dictionary(); + + foreach (var deviceTag in deviceTags) + { + if (mappingDict.ContainsKey(deviceTag.Id)) + { + var mapping = mappingDict[deviceTag.Id]; + var mappedTag = ApplyTagMapping(deviceTag, mapping); + result[mapping.SystemTagId] = mappedTag; + } + } + + await _loggingService.LogInformationAsync($"Mapped {result.Count} tags from {deviceTags.Count()} device tags for template {templateId}"); + return result; + } + + public async Task ValidateTagMappingAsync(TagMapping mapping) + { + if (mapping == null) + throw new ArgumentNullException(nameof(mapping)); + + if (string.IsNullOrWhiteSpace(mapping.DeviceTagId)) + throw new ArgumentException("Device tag ID is required"); + + if (string.IsNullOrWhiteSpace(mapping.SystemTagId)) + throw new ArgumentException("System tag ID is required"); + + // Validate template exists + var template = await _templateRepository.GetByIdAsync(mapping.TemplateId); + if (template == null) + throw new KeyNotFoundException($"Template with ID {mapping.TemplateId} not found"); + + // Validate data type + if (!IsValidDataType(mapping.DataType)) + throw new ArgumentException($"Invalid data type: {mapping.DataType}"); + + // Check for duplicate mappings + var existingMapping = await _tagMappingRepository.GetByDeviceTagAndTemplateAsync(mapping.DeviceTagId, mapping.TemplateId); + if (existingMapping != null && existingMapping.MappingId != mapping.MappingId) + { + throw new InvalidOperationException($"Duplicate mapping found for tag {mapping.DeviceTagId} in template {mapping.TemplateId}"); + } + } + + public async Task> GetMappingsByTagIdAsync(string tagId) + { + return await _tagMappingRepository.GetMappingsByDeviceTagAsync(tagId); + } + + public async Task IsTagMappedAsync(string tagId, int templateId) + { + var mapping = await _tagMappingRepository.GetByDeviceTagAndTemplateAsync(tagId, templateId); + return mapping != null && mapping.IsActive; + } + + public async Task ValidateAndMapTagsAsync(IEnumerable deviceTags, int templateId) + { + var result = new TagMappingResult + { + TemplateId = templateId, + TotalDeviceTags = deviceTags.Count(), + MappedTags = 0, + UnmappedTags = new List(), + ConversionErrors = new List(), + MappedData = new Dictionary() + }; + + var template = await _templateRepository.GetByIdAsync(templateId); + if (template == null) + { + result.ConversionErrors.Add($"Template with ID {templateId} not found"); + return result; + } + + var mappings = await GetMappingsByTemplateAsync(templateId); + var mappingDict = mappings.ToDictionary(m => m.DeviceTagId); + + foreach (var deviceTag in deviceTags) + { + try + { + if (mappingDict.ContainsKey(deviceTag.Id)) + { + var mapping = mappingDict[deviceTag.Id]; + if (mapping.IsActive) + { + var mappedTag = ApplyTagMapping(deviceTag, mapping); + result.MappedData[mapping.SystemTagId] = mappedTag; + result.MappedTags++; + } + else + { + result.UnmappedTags.Add(deviceTag.Id); + } + } + else + { + result.UnmappedTags.Add(deviceTag.Id); + } + } + catch (Exception ex) + { + result.ConversionErrors.Add($"Failed to map tag {deviceTag.Id}: {ex.Message}"); + } + } + + await _loggingService.LogInformationAsync($"Tag mapping validation: {result.MappedTags}/{result.TotalDeviceTags} tags mapped for template {templateId}"); + return result; + } + + private TagData ApplyTagMapping(TagData deviceTag, TagMapping mapping) + { + var mappedTag = new TagData + { + Id = mapping.SystemTagId, + Desc = mapping.Description ?? deviceTag.Desc, + Quality = deviceTag.Quality, + Time = deviceTag.Time + }; + + // Apply data type conversion + mappedTag.Value = ConvertTagValue(deviceTag.Value, mapping.DataType, mapping.ConversionFormula); + + return mappedTag; + } + + private object ConvertTagValue(object value, string dataType, string conversionFormula) + { + if (value == null) + return null; + + try + { + // Apply conversion formula if provided + if (!string.IsNullOrEmpty(conversionFormula)) + { + value = ApplyConversionFormula(value, conversionFormula); + } + + // Convert to target data type + switch (dataType.ToLower()) + { + case "int": + if (int.TryParse(value.ToString(), out int intValue)) + return intValue; + throw new ArgumentException($"Cannot convert {value} to int"); + + case "decimal": + if (decimal.TryParse(value.ToString(), out decimal decimalValue)) + return decimalValue; + throw new ArgumentException($"Cannot convert {value} to decimal"); + + case "bool": + if (bool.TryParse(value.ToString(), out bool boolValue)) + return boolValue; + return Convert.ToBoolean(value); + + case "string": + return value.ToString(); + + case "datetime": + if (DateTime.TryParse(value.ToString(), out DateTime dateTimeValue)) + return dateTimeValue; + throw new ArgumentException($"Cannot convert {value} to datetime"); + + default: + return value; + } + } + catch (Exception ex) + { + throw new ArgumentException($"Failed to convert tag value: {ex.Message}"); + } + } + + private object ApplyConversionFormula(object value, string formula) + { + // Simple formula evaluation (in real implementation, use expression parser) + if (value == null || string.IsNullOrEmpty(formula)) + return value; + + var valueStr = value.ToString(); + + // Handle basic arithmetic operations + if (formula.Contains("*")) + { + var parts = formula.Split('*'); + if (parts.Length == 2 && double.TryParse(parts[1], out double factor)) + { + if (double.TryParse(valueStr, out double numericValue)) + return numericValue * factor; + } + } + else if (formula.Contains("/")) + { + var parts = formula.Split('/'); + if (parts.Length == 2 && double.TryParse(parts[1], out double divisor)) + { + if (double.TryParse(valueStr, out double numericValue) && divisor != 0) + return numericValue / divisor; + } + } + else if (formula.Contains("+")) + { + var parts = formula.Split('+'); + if (parts.Length == 2 && double.TryParse(parts[1], out double offset)) + { + if (double.TryParse(valueStr, out double numericValue)) + return numericValue + offset; + } + } + else if (formula.Contains("-")) + { + var parts = formula.Split('-'); + if (parts.Length == 2 && double.TryParse(parts[1], out double offset)) + { + if (double.TryParse(valueStr, out double numericValue)) + return numericValue - offset; + } + } + + return value; + } + + private bool IsValidDataType(string dataType) + { + if (string.IsNullOrWhiteSpace(dataType)) + return false; + + var validTypes = new[] { "int", "decimal", "bool", "string", "datetime", "double" }; + return validTypes.Contains(dataType.ToLower()); + } + } + + // Supporting classes and interfaces + public class TagMappingResult + { + public int TemplateId { get; set; } + public int TotalDeviceTags { get; set; } + public int MappedTags { get; set; } + public List UnmappedTags { get; set; } + public List ConversionErrors { get; set; } + public Dictionary MappedData { get; set; } + } + + // Additional repository interface for tag mappings + public interface ITagMappingRepository : IRepository + { + Task> GetMappingsByTemplateAsync(int templateId); + Task GetByDeviceTagAndTemplateAsync(string deviceTagId, int templateId); + Task> GetMappingsByDeviceTagAsync(string deviceTagId); + Task> GetActiveMappingsAsync(); + Task DeviceTagExistsAsync(string deviceTagId, int templateId); + } +} \ No newline at end of file diff --git a/Haoliang.Core/Services/TemplateValidationService.cs b/Haoliang.Core/Services/TemplateValidationService.cs new file mode 100644 index 0000000..a9f3c47 --- /dev/null +++ b/Haoliang.Core/Services/TemplateValidationService.cs @@ -0,0 +1,675 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Haoliang.Models.Template; +using Haoliang.Models.Device; +using Haoliang.Models.DataCollection; +using Haoliang.Data.Repositories; + +namespace Haoliang.Core.Services +{ + public interface ITemplateValidationService + { + Task ValidateTemplateStructureAsync(CNCBrandTemplate template); + Task ValidateTagMappingsAsync(CNCBrandTemplate template); + Task ValidateDataParsingRulesAsync(CNCBrandTemplate template); + Task> ValidateTemplateForDeviceAsync(int templateId, int deviceId); + Task TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData); + Task> GetMissingRequiredTagsAsync(CNCBrandTemplate template); + Task ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template); + Task> ValidateDeviceDataAsync(IEnumerable deviceTags, int templateId); + Task ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2); + } + + public class TemplateValidationService : ITemplateValidationService + { + private readonly ITemplateRepository _templateRepository; + private readonly ITagMappingRepository _tagMappingRepository; + private readonly ILoggingService _loggingService; + + public TemplateValidationService( + ITemplateRepository templateRepository, + ITagMappingRepository tagMappingRepository, + ILoggingService loggingService) + { + _templateRepository = templateRepository; + _tagMappingRepository = tagMappingRepository; + _loggingService = loggingService; + } + + public async Task ValidateTemplateStructureAsync(CNCBrandTemplate template) + { + var errors = new List(); + + // Check basic structure + if (template == null) + { + errors.Add(new ValidationError { Field = "template", Message = "Template cannot be null" }); + return false; + } + + if (string.IsNullOrWhiteSpace(template.TemplateName)) + { + errors.Add(new ValidationError { Field = "templateName", Message = "Template name is required" }); + } + + if (string.IsNullOrWhiteSpace(template.BrandName)) + { + errors.Add(new ValidationError { Field = "brandName", Message = "Brand name is required" }); + } + + if (template.Tags == null || !template.Tags.Any()) + { + errors.Add(new ValidationError { Field = "tags", Message = "At least one tag must be defined" }); + } + + // Validate tag structure + if (template.Tags != null) + { + for (int i = 0; i < template.Tags.Count; i++) + { + var tag = template.Tags[i]; + var tagErrors = ValidateTagStructure(tag, $"tags[{i}]"); + errors.AddRange(tagErrors); + } + } + + // Validate data processing rules + if (template.DataProcessingRules != null) + { + foreach (var rule in template.DataProcessingRules) + { + var ruleErrors = ValidateDataProcessingRule(rule); + errors.AddRange(ruleErrors); + } + } + + // Log validation results + if (errors.Any()) + { + await _loggingService.LogWarningAsync($"Template structure validation failed with {errors.Count} errors"); + } + else + { + await _loggingService.LogInfoAsync("Template structure validation passed"); + } + + return !errors.Any(); + } + + public async Task ValidateTagMappingsAsync(CNCBrandTemplate template) + { + var errors = new List(); + + if (template.Tags == null || !template.Tags.Any()) + { + errors.Add(new ValidationError { Field = "tags", Message = "No tags defined to validate" }); + return false; + } + + var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId); + var mappingDict = mappings.ToDictionary(m => m.DeviceTagId); + + foreach (var tag in template.Tags) + { + if (!string.IsNullOrWhiteSpace(tag.DeviceTagId)) + { + // Check if mapping exists for device tag + if (!mappingDict.ContainsKey(tag.DeviceTagId)) + { + errors.Add(new ValidationError + { + Field = $"tags[{tag.SystemTagId}].deviceTagId", + Message = $"No mapping found for device tag '{tag.DeviceTagId}'" + }); + } + else + { + var mapping = mappingDict[tag.DeviceTagId]; + if (!mapping.IsActive) + { + errors.Add(new ValidationError + { + Field = $"tags[{tag.SystemTagId}].deviceTagId", + Message = $"Mapping for device tag '{tag.DeviceTagId}' is inactive" + }); + } + } + } + } + + return !errors.Any(); + } + + public async Task ValidateDataParsingRulesAsync(CNCBrandTemplate template) + { + var errors = new List(); + + if (template.DataProcessingRules == null || !template.DataProcessingRules.Any()) + { + return true; // No rules to validate + } + + foreach (var rule in template.DataProcessingRules) + { + var ruleErrors = ValidateDataProcessingRule(rule); + errors.AddRange(ruleErrors); + } + + return !errors.Any(); + } + + public async Task> ValidateTemplateForDeviceAsync(int templateId, int deviceId) + { + var errors = new List(); + + var template = await _templateRepository.GetByIdAsync(templateId); + if (template == null) + { + errors.Add(new ValidationError { Field = "template", Message = "Template not found" }); + return errors; + } + + var device = await _templateRepository.GetDeviceByIdAsync(deviceId); // Assuming this method exists + if (device == null) + { + errors.Add(new ValidationError { Field = "device", Message = "Device not found" }); + return errors; + } + + // Check if template is compatible with device brand + if (template.BrandName != device.DeviceBrand) + { + errors.Add(new ValidationError + { + Field = "template.brand", + Message = $"Template brand '{template.BrandName}' does not match device brand '{device.DeviceBrand}'" + }); + } + + // Check for missing required tags + var missingTags = await GetMissingRequiredTagsAsync(template); + if (missingTags.Any()) + { + errors.Add(new ValidationError + { + Field = "tags", + Message = $"Missing required tags: {string.Join(", ", missingTags)}" + }); + } + + return errors; + } + + public async Task TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData) + { + try + { + // Parse sample data + var document = JsonDocument.Parse(sampleData); + var root = document.RootElement; + + // Extract device data + var deviceTags = new List(); + + if (root.TryGetProperty("tags", out var tagsElement)) + { + foreach (var tagElement in tagsElement.EnumerateArray()) + { + var tag = new TagData + { + Id = tagElement.GetProperty("id").GetString(), + Desc = tagElement.GetProperty("desc").GetString(), + Quality = tagElement.GetProperty("quality").GetString(), + Time = DateTime.Parse(tagElement.GetProperty("time").GetString()) + }; + + if (tagElement.TryGetProperty("value", out var valueElement)) + { + tag.Value = ParseTagValue(valueElement); + } + + deviceTags.Add(tag); + } + } + + // Test tag mapping + var mappingResult = await ValidateDeviceDataAsync(deviceTags, template.TemplateId); + if (mappingResult.Any(r => !r.IsValid)) + { + await _loggingService.LogWarningAsync($"Template data parsing test failed with {mappingResult.Count(r => !r.IsValid)} validation errors"); + return false; + } + + // Test data processing rules + if (template.DataProcessingRules != null) + { + foreach (var rule in template.DataProcessingRules) + { + if (!await ExecuteDataProcessingRuleAsync(rule, deviceTags)) + { + await _loggingService.LogWarningAsync($"Data processing rule '{rule.RuleName}' failed during test"); + return false; + } + } + } + + await _loggingService.LogInfoAsync("Template data parsing test passed"); + return true; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Template data parsing test failed: {ex.Message}", ex); + return false; + } + } + + public async Task> GetMissingRequiredTagsAsync(CNCBrandTemplate template) + { + var missingTags = new List(); + + if (template == null || template.Tags == null) + return missingTags; + + var requiredTags = template.Tags + .Where(t => t.IsRequired) + .Select(t => t.DeviceTagId) + .Where(id => !string.IsNullOrWhiteSpace(id)) + .ToList(); + + if (!requiredTags.Any()) + return missingTags; + + var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId); + var mappedTags = mappings + .Where(m => m.IsActive) + .Select(m => m.DeviceTagId) + .ToHashSet(); + + foreach (var requiredTag in requiredTags) + { + if (!mappedTags.Contains(requiredTag)) + { + missingTags.Add(requiredTag); + } + } + + return missingTags; + } + + public async Task ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template) + { + var report = new ValidationReport + { + TemplateId = template.TemplateId, + TemplateName = template.TemplateName, + ValidationTime = DateTime.Now, + Checks = new List + { + new ValidationCheck + { + Name = "Structure Validation", + Passed = await ValidateTemplateStructureAsync(template), + Errors = new List() + }, + new ValidationCheck + { + Name = "Tag Mapping Validation", + Passed = await ValidateTagMappingsAsync(template), + Errors = new List() + }, + new ValidationCheck + { + Name = "Data Parsing Rules Validation", + Passed = await ValidateDataProcessingRulesAsync(template), + Errors = new List() + } + } + }; + + // Aggregate all errors + report.HasErrors = report.Checks.Any(c => !c.Passed); + report.TotalChecks = report.Checks.Count; + report.PassedChecks = report.Checks.Count(c => c.Passed); + report.FailedChecks = report.Checks.Count(c => !c.Passed); + + await _loggingService.LogInformationAsync($"Comprehensive template validation: {report.PassedChecks}/{report.TotalChecks} checks passed"); + return report; + } + + public async Task> ValidateDeviceDataAsync(IEnumerable deviceTags, int templateId) + { + var results = new List(); + var template = await _templateRepository.GetByIdAsync(templateId); + + if (template == null) + { + results.Add(new TagValidationResult + { + SystemTagId = "template", + IsValid = false, + ErrorMessage = "Template not found" + }); + return results; + } + + var templateTags = template.Tags ?? new List(); + var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(templateId); + var mappingDict = mappings.ToDictionary(m => m.DeviceTagId); + + foreach (var templateTag in templateTags) + { + var result = new TagValidationResult + { + SystemTagId = templateTag.SystemTagId, + ExpectedDataType = templateTag.DataType, + IsRequired = templateTag.IsRequired + }; + + if (!string.IsNullOrWhiteSpace(templateTag.DeviceTagId)) + { + // Find corresponding device tag + var deviceTag = deviceTags.FirstOrDefault(t => t.Id == templateTag.DeviceTagId); + + if (deviceTag == null) + { + if (templateTag.IsRequired) + { + result.IsValid = false; + result.ErrorMessage = $"Required device tag '{templateTag.DeviceTagId}' not found"; + } + else + { + result.IsValid = true; + result.Message = "Optional tag not present"; + } + } + else + { + // Validate device tag + result.DeviceTagId = templateTag.DeviceTagId; + result.DeviceValue = deviceTag.Value; + result.Quality = deviceTag.Quality; + + if (!ValidateTagValue(deviceTag, templateTag)) + { + result.IsValid = false; + result.ErrorMessage = $"Tag value validation failed for '{templateTag.DeviceTagId}'"; + } + else + { + result.IsValid = true; + result.MappedValue = ApplyTagMapping(deviceTag, mappingDict[templateTag.DeviceTagId]); + } + } + } + else + { + result.IsValid = true; + result.Message = "No device tag mapping defined"; + } + + results.Add(result); + } + + return results; + } + + public async Task ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2) + { + if (template1 == null || template2 == null) + return false; + + if (template1.TemplateId == template2.TemplateId) + return true; // Same template is always compatible + + // Check if same brand + if (template1.BrandName != template2.BrandName) + { + await _loggingService.LogInfoAsync($"Templates have different brands: {template1.BrandName} vs {template2.BrandName}"); + return false; + } + + // Check tag compatibility + var tags1 = template1.Tags ?? new List(); + var tags2 = template2.Tags ?? new List(); + + // Count common tags + var commonTags = tags1.Intersect(tags2, new TagTemplateComparer()); + var compatibilityScore = (double)commonTags.Count() / Math.Max(tags1.Count, tags2.Count); + + await _loggingService.LogInfoAsync($"Template compatibility score: {compatibilityScore:P1}"); + return compatibilityScore >= 0.8; // 80% compatibility threshold + } + + private List ValidateTagStructure(TagTemplate tag, string fieldPath) + { + var errors = new List(); + + if (string.IsNullOrWhiteSpace(tag.SystemTagId)) + { + errors.Add(new ValidationError { Field = $"{fieldPath}.systemTagId", Message = "System tag ID is required" }); + } + + if (!string.IsNullOrWhiteSpace(tag.DeviceTagId)) + { + if (string.IsNullOrWhiteSpace(tag.DataType)) + { + errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = "Data type is required when device tag ID is specified" }); + } + } + + if (!string.IsNullOrWhiteSpace(tag.DataType)) + { + var validTypes = new[] { "int", "decimal", "bool", "string", "datetime", "double" }; + if (!validTypes.Contains(tag.DataType.ToLower())) + { + errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = $"Invalid data type: {tag.DataType}" }); + } + } + + // Validate regex pattern if provided + if (!string.IsNullOrWhiteSpace(tag.ValidationRegex)) + { + try + { + Regex.IsMatch("test", tag.ValidationRegex); // Test if regex is valid + } + catch + { + errors.Add(new ValidationError { Field = $"{fieldPath}.validationRegex", Message = "Invalid regular expression pattern" }); + } + } + + return errors; + } + + private List ValidateDataProcessingRule(DataProcessingRule rule) + { + var errors = new List(); + + if (string.IsNullOrWhiteSpace(rule.RuleName)) + { + errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].ruleName", Message = "Rule name is required" }); + } + + if (string.IsNullOrWhiteSpace(rule.Condition)) + { + errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Condition is required" }); + } + + if (string.IsNullOrWhiteSpace(rule.Action)) + { + errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].action", Message = "Action is required" }); + } + + // Validate condition syntax (simple check) + if (!string.IsNullOrWhiteSpace(rule.Condition) && !IsValidCondition(rule.Condition)) + { + errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Invalid condition syntax" }); + } + + return errors; + } + + private bool IsValidCondition(string condition) + { + // Simple condition validation (in real implementation, use expression parser) + var validOperators = new[] { ">", "<", ">=", "<=", "==", "!=", "&&", "||", "(", ")", "true", "false" }; + var parts = condition.Split(' '); + + foreach (var part in parts) + { + if (!string.IsNullOrWhiteSpace(part) && !validOperators.Contains(part) && !double.TryParse(part, out _)) + { + return false; + } + } + + return true; + } + + private object ParseTagValue(JsonElement valueElement) + { + if (valueElement.ValueKind == JsonValueKind.String) + { + return valueElement.GetString(); + } + else if (valueElement.ValueKind == JsonValueKind.Number) + { + if (valueElement.TryGetInt32(out int intValue)) + return intValue; + else if (valueElement.TryGetDecimal(out decimal decimalValue)) + return decimalValue; + else + return valueElement.GetDouble(); + } + else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False) + { + return valueElement.GetBoolean(); + } + else + { + return valueElement.ToString(); + } + } + + private bool ValidateTagValue(TagData deviceTag, TagTemplate templateTag) + { + if (deviceTag.Value == null) + return !templateTag.IsRequired; + + try + { + // Validate data type + switch (templateTag.DataType.ToLower()) + { + case "int": + return int.TryParse(deviceTag.Value.ToString(), out _); + case "decimal": + return decimal.TryParse(deviceTag.Value.ToString(), out _); + case "bool": + return bool.TryParse(deviceTag.Value.ToString(), out _); + case "datetime": + return DateTime.TryParse(deviceTag.Value.ToString(), out _); + default: + return true; // String type accepts any value + } + } + catch + { + return false; + } + } + + private TagData ApplyTagMapping(TagData deviceTag, TagMapping mapping) + { + return new TagData + { + Id = mapping.SystemTagId, + Desc = mapping.Description ?? deviceTag.Desc, + Quality = deviceTag.Quality, + Time = deviceTag.Time, + Value = ConvertTagValue(deviceTag.Value, mapping.DataType, mapping.ConversionFormula) + }; + } + + private async Task ExecuteDataProcessingRuleAsync(DataProcessingRule rule, IEnumerable deviceTags) + { + // Simple rule execution (in real implementation, use expression parser) + try + { + // This is a simplified implementation + // In a real system, you would parse and execute the condition and action + + if (rule.Condition.Contains("temperature") && deviceTags.Any(t => t.Id == "temperature")) + { + var tempTag = deviceTags.First(t => t.Id == "temperature"); + var tempValue = Convert.ToDouble(tempTag.Value); + + if (tempValue > 100 && rule.Action.Contains("alert")) + { + return true; // Rule executed successfully + } + } + + return true; + } + catch (Exception ex) + { + await _loggingService.LogErrorAsync($"Failed to execute data processing rule '{rule.RuleName}': {ex.Message}"); + return false; + } + } + } + + // Supporting classes and interfaces + public class ValidationReport + { + public int TemplateId { get; set; } + public string TemplateName { get; set; } + public DateTime ValidationTime { get; set; } + public List Checks { get; set; } + public bool HasErrors { get; set; } + public int TotalChecks { get; set; } + public int PassedChecks { get; set; } + public int FailedChecks { get; set; } + } + + public class ValidationCheck + { + public string Name { get; set; } + public bool Passed { get; set; } + public List Errors { get; set; } + } + + public class TagValidationResult + { + public string SystemTagId { get; set; } + public string DeviceTagId { get; set; } + public object DeviceValue { get; set; } + public object MappedValue { get; set; } + public string ExpectedDataType { get; set; } + public string Quality { get; set; } + public bool IsRequired { get; set; } + public bool IsValid { get; set; } + public string ErrorMessage { get; set; } + public string Message { get; set; } + } + + public class TagTemplateComparer : IEqualityComparer + { + public bool Equals(TagTemplate x, TagTemplate y) + { + return x?.SystemTagId == y?.SystemTagId; + } + + public int GetHashCode(TagTemplate obj) + { + return obj?.SystemTagId?.GetHashCode() ?? 0; + } + } +} \ No newline at end of file diff --git a/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.assets.cache b/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.assets.cache index 0fcff8a..5aa1dfb 100644 Binary files a/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.assets.cache and b/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.assets.cache differ diff --git a/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.AssemblyReference.cache b/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.AssemblyReference.cache index 7908de0..91f44cf 100644 Binary files a/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.AssemblyReference.cache and b/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.AssemblyReference.cache differ diff --git a/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.CoreCompileInputs.cache b/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.CoreCompileInputs.cache index 0a53151..720b943 100644 --- a/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.CoreCompileInputs.cache +++ b/Haoliang.Core/obj/Debug/net6.0/Haoliang.Core.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -457d24fdacb9f610a90060c935459105dbda72db +e368b60e772fcd0825fb3ea651c39d72f525e1de diff --git a/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.dgspec.json b/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.dgspec.json index 6a49655..a30118c 100644 --- a/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.dgspec.json +++ b/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.dgspec.json @@ -41,6 +41,36 @@ "frameworks": { "net6.0": { "targetAlias": "net6.0", + "dependencies": { + "BCrypt.Net-Next": { + "target": "Package", + "version": "[4.0.3, )" + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "target": "Package", + "version": "[2.2.0, )" + }, + "Microsoft.AspNetCore.SignalR": { + "target": "Package", + "version": "[1.1.0, )" + }, + "Microsoft.Extensions.Caching.Memory": { + "target": "Package", + "version": "[6.0.0, )" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "target": "Package", + "version": "[6.0.0, )" + }, + "Microsoft.IdentityModel.Tokens": { + "target": "Package", + "version": "[6.26.0, )" + }, + "System.IdentityModel.Tokens.Jwt": { + "target": "Package", + "version": "[6.26.0, )" + } + }, "imports": [ "net461", "net462", diff --git a/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.g.targets b/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.g.targets index 3dc06ef..fc748e7 100644 --- a/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.g.targets +++ b/Haoliang.Core/obj/Haoliang.Core.csproj.nuget.g.targets @@ -1,2 +1,6 @@  - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/Haoliang.Core/obj/project.assets.json b/Haoliang.Core/obj/project.assets.json index cb1d05f..dd7e166 100644 --- a/Haoliang.Core/obj/project.assets.json +++ b/Haoliang.Core/obj/project.assets.json @@ -2,6 +2,742 @@ "version": 3, "targets": { "net6.0": { + "BCrypt.Net-Next/4.0.3": { + "type": "package", + "compile": { + "lib/net6.0/BCrypt.Net-Next.dll": {} + }, + "runtime": { + "lib/net6.0/BCrypt.Net-Next.dll": {} + } + }, + "Microsoft.AspNetCore.Authentication.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Authorization/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll": {} + } + }, + "Microsoft.AspNetCore.Authorization.Policy/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Authorization": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll": {} + } + }, + "Microsoft.AspNetCore.Connections.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.IO.Pipelines": "4.5.2" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Hosting.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Hosting.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Http/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.AspNetCore.WebUtilities": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Connections/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Authorization.Policy": "2.2.0", + "Microsoft.AspNetCore.Hosting.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http": "2.2.0", + "Microsoft.AspNetCore.Http.Connections.Common": "1.1.0", + "Microsoft.AspNetCore.Routing": "2.2.0", + "Microsoft.AspNetCore.WebSockets": "2.2.0", + "Newtonsoft.Json": "11.0.2", + "System.Security.Principal.Windows": "4.5.0" + }, + "compile": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.dll": {} + }, + "runtime": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Connections.Common/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Connections.Abstractions": "2.2.0", + "Newtonsoft.Json": "11.0.2", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Extensions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Features/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {} + } + }, + "Microsoft.AspNetCore.Routing/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + }, + "compile": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.dll": {} + }, + "runtime": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.dll": {} + } + }, + "Microsoft.AspNetCore.Routing.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Connections": "1.1.0", + "Microsoft.AspNetCore.SignalR.Core": "1.1.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR.Common/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Connections.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Newtonsoft.Json": "11.0.2", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.dll": {} + }, + "runtime": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR.Core/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Authorization": "2.2.0", + "Microsoft.AspNetCore.SignalR.Common": "1.1.0", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "1.1.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "System.Reflection.Emit": "4.3.0", + "System.Threading.Channels": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR.Protocols.Json/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.SignalR.Common": "1.1.0", + "Newtonsoft.Json": "11.0.2" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.dll": {} + } + }, + "Microsoft.AspNetCore.WebSockets/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "System.Net.WebSockets.WebSocketProtocol": "4.5.1" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.dll": {} + } + }, + "Microsoft.AspNetCore.WebUtilities/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": {} + } + }, + "Microsoft.CSharp/4.5.0": { + "type": "package", + "compile": { + "ref/netcoreapp2.0/_._": {} + }, + "runtime": { + "lib/netcoreapp2.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Abstractions/6.0.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "6.0.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll": {} + } + }, + "Microsoft.Extensions.Caching.Memory/6.0.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {} + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "Microsoft.Extensions.FileProviders.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {} + } + }, + "Microsoft.Extensions.Hosting.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/Microsoft.Extensions.Logging.Abstractions.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Extensions.Logging.Abstractions.dll": {} + }, + "build": { + "build/Microsoft.Extensions.Logging.Abstractions.targets": {}, + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "Microsoft.Extensions.ObjectPool/2.2.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": {} + } + }, + "Microsoft.Extensions.Options/6.0.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" + }, + "compile": { + "lib/netstandard2.1/Microsoft.Extensions.Options.dll": {} + }, + "runtime": { + "lib/netstandard2.1/Microsoft.Extensions.Options.dll": {} + } + }, + "Microsoft.Extensions.Primitives/6.0.0": { + "type": "package", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "compile": { + "lib/net6.0/Microsoft.Extensions.Primitives.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Extensions.Primitives.dll": {} + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "Microsoft.IdentityModel.Abstractions/6.26.0": { + "type": "package", + "compile": { + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll": {} + } + }, + "Microsoft.IdentityModel.JsonWebTokens/6.26.0": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "6.26.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.7.2" + }, + "compile": { + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll": {} + } + }, + "Microsoft.IdentityModel.Logging/6.26.0": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.26.0" + }, + "compile": { + "lib/net6.0/Microsoft.IdentityModel.Logging.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Logging.dll": {} + } + }, + "Microsoft.IdentityModel.Tokens/6.26.0": { + "type": "package", + "dependencies": { + "Microsoft.CSharp": "4.5.0", + "Microsoft.IdentityModel.Logging": "6.26.0", + "System.Security.Cryptography.Cng": "4.5.0" + }, + "compile": { + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll": {} + } + }, + "Microsoft.Net.Http.Headers/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": {} + } + }, + "Microsoft.NETCore.Platforms/2.0.0": { + "type": "package", + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "Microsoft.NETCore.Targets/1.1.0": { + "type": "package", + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "Newtonsoft.Json/11.0.2": { + "type": "package", + "compile": { + "lib/netstandard2.0/Newtonsoft.Json.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": {} + } + }, + "System.Buffers/4.5.0": { + "type": "package", + "compile": { + "ref/netcoreapp2.0/_._": {} + }, + "runtime": { + "lib/netcoreapp2.0/_._": {} + } + }, + "System.IdentityModel.Tokens.Jwt/6.26.0": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "6.26.0", + "Microsoft.IdentityModel.Tokens": "6.26.0" + }, + "compile": { + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll": {} + }, + "runtime": { + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll": {} + } + }, + "System.IO/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + }, + "compile": { + "ref/netstandard1.5/System.IO.dll": {} + } + }, + "System.IO.Pipelines/4.5.2": { + "type": "package", + "compile": { + "ref/netstandard1.3/System.IO.Pipelines.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.IO.Pipelines.dll": {} + } + }, + "System.Net.WebSockets.WebSocketProtocol/4.5.1": { + "type": "package", + "compile": { + "ref/netstandard2.0/System.Net.WebSockets.WebSocketProtocol.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.Net.WebSockets.WebSocketProtocol.dll": {} + } + }, + "System.Reflection/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.5/System.Reflection.dll": {} + } + }, + "System.Reflection.Emit/4.3.0": { + "type": "package", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.1/System.Reflection.Emit.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.dll": {} + } + }, + "System.Reflection.Emit.ILGeneration/4.3.0": { + "type": "package", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} + } + }, + "System.Reflection.Primitives/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.0/System.Reflection.Primitives.dll": {} + } + }, + "System.Runtime/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + }, + "compile": { + "ref/netstandard1.5/System.Runtime.dll": {} + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {} + }, + "runtime": { + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {} + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "System.Security.Cryptography.Cng/4.5.0": { + "type": "package", + "compile": { + "ref/netcoreapp2.1/System.Security.Cryptography.Cng.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll": {} + }, + "runtimeTargets": { + "runtimes/win/lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Security.Principal.Windows/4.5.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.0.0" + }, + "compile": { + "ref/netstandard2.0/System.Security.Principal.Windows.dll": {} + }, + "runtime": { + "lib/netstandard2.0/System.Security.Principal.Windows.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netcoreapp2.0/System.Security.Principal.Windows.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netcoreapp2.0/System.Security.Principal.Windows.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Text.Encoding/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.3/System.Text.Encoding.dll": {} + } + }, + "System.Text.Encodings.Web/4.7.2": { + "type": "package", + "compile": { + "lib/netstandard2.1/System.Text.Encodings.Web.dll": {} + }, + "runtime": { + "lib/netstandard2.1/System.Text.Encodings.Web.dll": {} + } + }, + "System.Text.Json/4.7.2": { + "type": "package", + "compile": { + "lib/netcoreapp3.0/System.Text.Json.dll": {} + }, + "runtime": { + "lib/netcoreapp3.0/System.Text.Json.dll": {} + } + }, + "System.Threading.Channels/4.5.0": { + "type": "package", + "compile": { + "lib/netcoreapp2.1/System.Threading.Channels.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.Threading.Channels.dll": {} + } + }, + "System.Threading.Tasks/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.3/System.Threading.Tasks.dll": {} + } + }, "Haoliang.Models/1.0.0": { "type": "project", "framework": ".NETCoreApp,Version=v6.0", @@ -15,6 +751,1559 @@ } }, "libraries": { + "BCrypt.Net-Next/4.0.3": { + "sha512": "W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==", + "type": "package", + "path": "bcrypt.net-next/4.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "bcrypt.net-next.4.0.3.nupkg.sha512", + "bcrypt.net-next.nuspec", + "ico.png", + "lib/net20/BCrypt.Net-Next.dll", + "lib/net20/BCrypt.Net-Next.xml", + "lib/net35/BCrypt.Net-Next.dll", + "lib/net35/BCrypt.Net-Next.xml", + "lib/net462/BCrypt.Net-Next.dll", + "lib/net462/BCrypt.Net-Next.xml", + "lib/net472/BCrypt.Net-Next.dll", + "lib/net472/BCrypt.Net-Next.xml", + "lib/net48/BCrypt.Net-Next.dll", + "lib/net48/BCrypt.Net-Next.xml", + "lib/net5.0/BCrypt.Net-Next.dll", + "lib/net5.0/BCrypt.Net-Next.xml", + "lib/net6.0/BCrypt.Net-Next.dll", + "lib/net6.0/BCrypt.Net-Next.xml", + "lib/netstandard2.0/BCrypt.Net-Next.dll", + "lib/netstandard2.0/BCrypt.Net-Next.xml", + "lib/netstandard2.1/BCrypt.Net-Next.dll", + "lib/netstandard2.1/BCrypt.Net-Next.xml", + "readme.md" + ] + }, + "Microsoft.AspNetCore.Authentication.Abstractions/2.2.0": { + "sha512": "VloMLDJMf3n/9ic5lCBOa42IBYJgyB1JhzLsL68Zqg+2bEPWfGBj/xCJy/LrKTArN0coOcZp3wyVTZlx0y9pHQ==", + "type": "package", + "path": "microsoft.aspnetcore.authentication.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.xml", + "microsoft.aspnetcore.authentication.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.authentication.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Authorization/2.2.0": { + "sha512": "/L0W8H3jMYWyaeA9gBJqS/tSWBegP9aaTM0mjRhxTttBY9z4RVDRYJ2CwPAmAXIuPr3r1sOw+CS8jFVRGHRezQ==", + "type": "package", + "path": "microsoft.aspnetcore.authorization/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.xml", + "microsoft.aspnetcore.authorization.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.authorization.nuspec" + ] + }, + "Microsoft.AspNetCore.Authorization.Policy/2.2.0": { + "sha512": "aJCo6niDRKuNg2uS2WMEmhJTooQUGARhV2ENQ2tO5443zVHUo19MSgrgGo9FIrfD+4yKPF8Q+FF33WkWfPbyKw==", + "type": "package", + "path": "microsoft.aspnetcore.authorization.policy/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.xml", + "microsoft.aspnetcore.authorization.policy.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.authorization.policy.nuspec" + ] + }, + "Microsoft.AspNetCore.Connections.Abstractions/2.2.0": { + "sha512": "Aqr/16Cu5XmGv7mLKJvXRxhhd05UJ7cTTSaUV4MZ3ynAzfgWjsAdpIU8FWuxwAjmVdmI8oOWuVDrbs+sRkhKnA==", + "type": "package", + "path": "microsoft.aspnetcore.connections.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.xml", + "microsoft.aspnetcore.connections.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.connections.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Hosting.Abstractions/2.2.0": { + "sha512": "ubycklv+ZY7Kutdwuy1W4upWcZ6VFR8WUXU7l7B2+mvbDBBPAcfpi+E+Y5GFe+Q157YfA3C49D2GCjAZc7Mobw==", + "type": "package", + "path": "microsoft.aspnetcore.hosting.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.xml", + "microsoft.aspnetcore.hosting.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.hosting.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.2.0": { + "sha512": "1PMijw8RMtuQF60SsD/JlKtVfvh4NORAhF4wjysdABhlhTrYmtgssqyncR0Stq5vqtjplZcj6kbT4LRTglt9IQ==", + "type": "package", + "path": "microsoft.aspnetcore.hosting.server.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.xml", + "microsoft.aspnetcore.hosting.server.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.hosting.server.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Http/2.2.0": { + "sha512": "YogBSMotWPAS/X5967pZ+yyWPQkThxhmzAwyCHCSSldzYBkW5W5d6oPfBaPqQOnSHYTpSOSOkpZoAce0vwb6+A==", + "type": "package", + "path": "microsoft.aspnetcore.http/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.xml", + "microsoft.aspnetcore.http.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Abstractions/2.2.0": { + "sha512": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "type": "package", + "path": "microsoft.aspnetcore.http.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.xml", + "microsoft.aspnetcore.http.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Connections/1.1.0": { + "sha512": "ZcwAM9rE5yjGC+vtiNAK0INybpKIqnvB+/rntZn2/CPtyiBAtovVrEp4UZOoC31zH5t0P78ix9gLNJzII/ODsA==", + "type": "package", + "path": "microsoft.aspnetcore.http.connections/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.dll", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.xml", + "microsoft.aspnetcore.http.connections.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.http.connections.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Connections.Common/1.1.0": { + "sha512": "mYk5QUUjyXQmlyDHWDjkLYDArt97plwe6KsDsNVhDEQ+HgZMKGjISyM6YSA7BERQNR25kXBTbIYfSy1vePGQgg==", + "type": "package", + "path": "microsoft.aspnetcore.http.connections.common/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.xml", + "microsoft.aspnetcore.http.connections.common.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.http.connections.common.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Extensions/2.2.0": { + "sha512": "2DgZ9rWrJtuR7RYiew01nGRzuQBDaGHGmK56Rk54vsLLsCdzuFUPqbDTJCS1qJQWTbmbIQ9wGIOjpxA1t0l7/w==", + "type": "package", + "path": "microsoft.aspnetcore.http.extensions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.xml", + "microsoft.aspnetcore.http.extensions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.extensions.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Features/2.2.0": { + "sha512": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "type": "package", + "path": "microsoft.aspnetcore.http.features/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.xml", + "microsoft.aspnetcore.http.features.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.features.nuspec" + ] + }, + "Microsoft.AspNetCore.Routing/2.2.0": { + "sha512": "jAhDBy0wryOnMhhZTtT9z63gJbvCzFuLm8yC6pHzuVu9ZD1dzg0ltxIwT4cfwuNkIL/TixdKsm3vpVOpG8euWQ==", + "type": "package", + "path": "microsoft.aspnetcore.routing/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.dll", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.xml", + "microsoft.aspnetcore.routing.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.routing.nuspec" + ] + }, + "Microsoft.AspNetCore.Routing.Abstractions/2.2.0": { + "sha512": "lRRaPN7jDlUCVCp9i0W+PB0trFaKB0bgMJD7hEJS9Uo4R9MXaMC8X2tJhPLmeVE3SGDdYI4QNKdVmhNvMJGgPQ==", + "type": "package", + "path": "microsoft.aspnetcore.routing.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.xml", + "microsoft.aspnetcore.routing.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.routing.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR/1.1.0": { + "sha512": "V5X5XkeAHaFyyBOGPrddVeqTNo6zRPJNS5PRhlzEyBXiNG9AtqUbMyWFdZahQyMiIWJau550z59A4kdC9g5I9A==", + "type": "package", + "path": "microsoft.aspnetcore.signalr/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.xml", + "microsoft.aspnetcore.signalr.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR.Common/1.1.0": { + "sha512": "TyLgQ4y4RVUIxiYFnHT181/rJ33/tL/NcBWC9BwLpulDt5/yGCG4EvsToZ49EBQ7256zj+R6OGw6JF+jj6MdPQ==", + "type": "package", + "path": "microsoft.aspnetcore.signalr.common/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.dll", + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Common.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Common.xml", + "microsoft.aspnetcore.signalr.common.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.common.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR.Core/1.1.0": { + "sha512": "mk69z50oFk2e89d3F/AfKeAvP3kvGG7MHG4ErydZiUd3ncSRq0kl0czq/COn/QVKYua9yGr2LIDwuR1C6/pu8Q==", + "type": "package", + "path": "microsoft.aspnetcore.signalr.core/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.xml", + "microsoft.aspnetcore.signalr.core.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.core.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR.Protocols.Json/1.1.0": { + "sha512": "BOsjatDJnvnnXCMajOlC0ISmiFnJi/EyJzMo0i//5fZJVCLrQ4fyV/HzrhhAhSJuwJOQDdDozKQ9MB9jHq84pg==", + "type": "package", + "path": "microsoft.aspnetcore.signalr.protocols.json/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.xml", + "microsoft.aspnetcore.signalr.protocols.json.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.protocols.json.nuspec" + ] + }, + "Microsoft.AspNetCore.WebSockets/2.2.0": { + "sha512": "ZpOcg2V0rCwU9ErfDb9y3Hcjoe7rU42XlmUS0mO4pVZQSgJVqR+DfyZtYd5LDa11F7bFNS2eezI9cBM3CmfGhw==", + "type": "package", + "path": "microsoft.aspnetcore.websockets/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.xml", + "microsoft.aspnetcore.websockets.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.websockets.nuspec" + ] + }, + "Microsoft.AspNetCore.WebUtilities/2.2.0": { + "sha512": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", + "type": "package", + "path": "microsoft.aspnetcore.webutilities/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.xml", + "microsoft.aspnetcore.webutilities.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.webutilities.nuspec" + ] + }, + "Microsoft.CSharp/4.5.0": { + "sha512": "kaj6Wb4qoMuH3HySFJhxwQfe8R/sJsNJnANrvv8WdFPMoNbKY5htfNscv+LHCu5ipz+49m2e+WQXpLXr9XYemQ==", + "type": "package", + "path": "microsoft.csharp/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/Microsoft.CSharp.dll", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.3/Microsoft.CSharp.dll", + "lib/netstandard2.0/Microsoft.CSharp.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/uap10.0.16299/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "microsoft.csharp.4.5.0.nupkg.sha512", + "microsoft.csharp.nuspec", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/Microsoft.CSharp.dll", + "ref/netcore50/Microsoft.CSharp.xml", + "ref/netcore50/de/Microsoft.CSharp.xml", + "ref/netcore50/es/Microsoft.CSharp.xml", + "ref/netcore50/fr/Microsoft.CSharp.xml", + "ref/netcore50/it/Microsoft.CSharp.xml", + "ref/netcore50/ja/Microsoft.CSharp.xml", + "ref/netcore50/ko/Microsoft.CSharp.xml", + "ref/netcore50/ru/Microsoft.CSharp.xml", + "ref/netcore50/zh-hans/Microsoft.CSharp.xml", + "ref/netcore50/zh-hant/Microsoft.CSharp.xml", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.0/Microsoft.CSharp.dll", + "ref/netstandard1.0/Microsoft.CSharp.xml", + "ref/netstandard1.0/de/Microsoft.CSharp.xml", + "ref/netstandard1.0/es/Microsoft.CSharp.xml", + "ref/netstandard1.0/fr/Microsoft.CSharp.xml", + "ref/netstandard1.0/it/Microsoft.CSharp.xml", + "ref/netstandard1.0/ja/Microsoft.CSharp.xml", + "ref/netstandard1.0/ko/Microsoft.CSharp.xml", + "ref/netstandard1.0/ru/Microsoft.CSharp.xml", + "ref/netstandard1.0/zh-hans/Microsoft.CSharp.xml", + "ref/netstandard1.0/zh-hant/Microsoft.CSharp.xml", + "ref/netstandard2.0/Microsoft.CSharp.dll", + "ref/netstandard2.0/Microsoft.CSharp.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/uap10.0.16299/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/6.0.0": { + "sha512": "bcz5sSFJbganH0+YrfvIjJDIcKNW7TL07C4d1eTmXy/wOt52iz4LVogJb6pazs7W0+74j0YpXFErvp++Aq5Bsw==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net461/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.6.0.0.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/6.0.0": { + "sha512": "Ve3BlCzhAlVp5IgO3+8dacAhZk1A0GlIlFNkAcfR2TfAibLKWIt5DhVJZfu4YtW+XZ89OjYf/agMcgjDtPxdGA==", + "type": "package", + "path": "microsoft.extensions.caching.memory/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/Microsoft.Extensions.Caching.Memory.dll", + "lib/net461/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.6.0.0.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/2.2.0": { + "sha512": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.2.2.0.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0": { + "sha512": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net461/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.FileProviders.Abstractions/2.2.0": { + "sha512": "EcnaSsPTqx2MGnHrmWOD0ugbuuqVT8iICqSqPzi45V5/MA1LjUNb0kwgcxBGqizV1R+WeBK7/Gw25Jzkyk9bIw==", + "type": "package", + "path": "microsoft.extensions.fileproviders.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "microsoft.extensions.fileproviders.abstractions.2.2.0.nupkg.sha512", + "microsoft.extensions.fileproviders.abstractions.nuspec" + ] + }, + "Microsoft.Extensions.Hosting.Abstractions/2.2.0": { + "sha512": "+k4AEn68HOJat5gj1TWa6X28WlirNQO9sPIIeQbia+91n03esEtMSSoekSTpMjUzjqtJWQN3McVx0GvSPFHF/Q==", + "type": "package", + "path": "microsoft.extensions.hosting.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "microsoft.extensions.hosting.abstractions.2.2.0.nupkg.sha512", + "microsoft.extensions.hosting.abstractions.nuspec" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/6.0.0": { + "sha512": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "build/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net461/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net6.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net6.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.ObjectPool/2.2.0": { + "sha512": "gA8H7uQOnM5gb+L0uTNjViHYr+hRDqCdfugheGo/MxQnuHzmhhzCBTIPm19qL1z1Xe0NEMabfcOBGv9QghlZ8g==", + "type": "package", + "path": "microsoft.extensions.objectpool/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll", + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.xml", + "microsoft.extensions.objectpool.2.2.0.nupkg.sha512", + "microsoft.extensions.objectpool.nuspec" + ] + }, + "Microsoft.Extensions.Options/6.0.0": { + "sha512": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "type": "package", + "path": "microsoft.extensions.options/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/Microsoft.Extensions.Options.dll", + "lib/net461/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.6.0.0.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/6.0.0": { + "sha512": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", + "type": "package", + "path": "microsoft.extensions.primitives/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/Microsoft.Extensions.Primitives.dll", + "lib/net461/Microsoft.Extensions.Primitives.xml", + "lib/net6.0/Microsoft.Extensions.Primitives.dll", + "lib/net6.0/Microsoft.Extensions.Primitives.xml", + "lib/netcoreapp3.1/Microsoft.Extensions.Primitives.dll", + "lib/netcoreapp3.1/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.6.0.0.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.IdentityModel.Abstractions/6.26.0": { + "sha512": "NHEnDBvLYqP81YWqKk1pJt0qSUmqobvFsRL/SR/H6x1jmQh2D1EcuHHhmfIzDnzaOlQJL9GeBDHykqHp0JGNCw==", + "type": "package", + "path": "microsoft.identitymodel.abstractions/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.Abstractions.dll", + "lib/net45/Microsoft.IdentityModel.Abstractions.xml", + "lib/net461/Microsoft.IdentityModel.Abstractions.dll", + "lib/net461/Microsoft.IdentityModel.Abstractions.xml", + "lib/net472/Microsoft.IdentityModel.Abstractions.dll", + "lib/net472/Microsoft.IdentityModel.Abstractions.xml", + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/net6.0/Microsoft.IdentityModel.Abstractions.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.xml", + "microsoft.identitymodel.abstractions.6.26.0.nupkg.sha512", + "microsoft.identitymodel.abstractions.nuspec" + ] + }, + "Microsoft.IdentityModel.JsonWebTokens/6.26.0": { + "sha512": "5S993Y51C6p3pQGcvJvUU4Bxq5H5tXGyAzvmXXZkELv8pSWVgbgVsQakGupjx6WLFRN+Y6clp9chVytynWYn5A==", + "type": "package", + "path": "microsoft.identitymodel.jsonwebtokens/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net45/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net461/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net461/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net472/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net472/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "microsoft.identitymodel.jsonwebtokens.6.26.0.nupkg.sha512", + "microsoft.identitymodel.jsonwebtokens.nuspec" + ] + }, + "Microsoft.IdentityModel.Logging/6.26.0": { + "sha512": "Svec5ltH4zz5ylAmFiHrUETLalw3d8siPbQ7+0H9GNGbZrVf5u7TaHpmDuJyb3EUiITfisD3vM83spsO/l1igA==", + "type": "package", + "path": "microsoft.identitymodel.logging/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.Logging.dll", + "lib/net45/Microsoft.IdentityModel.Logging.xml", + "lib/net461/Microsoft.IdentityModel.Logging.dll", + "lib/net461/Microsoft.IdentityModel.Logging.xml", + "lib/net472/Microsoft.IdentityModel.Logging.dll", + "lib/net472/Microsoft.IdentityModel.Logging.xml", + "lib/net6.0/Microsoft.IdentityModel.Logging.dll", + "lib/net6.0/Microsoft.IdentityModel.Logging.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Logging.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Logging.xml", + "microsoft.identitymodel.logging.6.26.0.nupkg.sha512", + "microsoft.identitymodel.logging.nuspec" + ] + }, + "Microsoft.IdentityModel.Tokens/6.26.0": { + "sha512": "mFNbROC89eap6GTqoYcInCiYsaV8sLxPsgCurQnJDcJoLBk7XoAJpBJae6rkj2VEzWqfErd4jlzaqqRI7wjGOQ==", + "type": "package", + "path": "microsoft.identitymodel.tokens/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.Tokens.dll", + "lib/net45/Microsoft.IdentityModel.Tokens.xml", + "lib/net461/Microsoft.IdentityModel.Tokens.dll", + "lib/net461/Microsoft.IdentityModel.Tokens.xml", + "lib/net472/Microsoft.IdentityModel.Tokens.dll", + "lib/net472/Microsoft.IdentityModel.Tokens.xml", + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll", + "lib/net6.0/Microsoft.IdentityModel.Tokens.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.xml", + "microsoft.identitymodel.tokens.6.26.0.nupkg.sha512", + "microsoft.identitymodel.tokens.nuspec" + ] + }, + "Microsoft.Net.Http.Headers/2.2.0": { + "sha512": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", + "type": "package", + "path": "microsoft.net.http.headers/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll", + "lib/netstandard2.0/Microsoft.Net.Http.Headers.xml", + "microsoft.net.http.headers.2.2.0.nupkg.sha512", + "microsoft.net.http.headers.nuspec" + ] + }, + "Microsoft.NETCore.Platforms/2.0.0": { + "sha512": "VdLJOCXhZaEMY7Hm2GKiULmn7IEPFE4XC5LPSfBVCUIA8YLZVh846gtfBJalsPQF2PlzdD7ecX7DZEulJ402ZQ==", + "type": "package", + "path": "microsoft.netcore.platforms/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netstandard1.0/_._", + "microsoft.netcore.platforms.2.0.0.nupkg.sha512", + "microsoft.netcore.platforms.nuspec", + "runtime.json", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "Microsoft.NETCore.Targets/1.1.0": { + "sha512": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", + "type": "package", + "path": "microsoft.netcore.targets/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "microsoft.netcore.targets.1.1.0.nupkg.sha512", + "microsoft.netcore.targets.nuspec", + "runtime.json" + ] + }, + "Newtonsoft.Json/11.0.2": { + "sha512": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==", + "type": "package", + "path": "newtonsoft.json/11.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "lib/portable-net40+sl5+win8+wp8+wpa81/Newtonsoft.Json.dll", + "lib/portable-net40+sl5+win8+wp8+wpa81/Newtonsoft.Json.xml", + "lib/portable-net45+win8+wp8+wpa81/Newtonsoft.Json.dll", + "lib/portable-net45+win8+wp8+wpa81/Newtonsoft.Json.xml", + "newtonsoft.json.11.0.2.nupkg.sha512", + "newtonsoft.json.nuspec" + ] + }, + "System.Buffers/4.5.0": { + "sha512": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==", + "type": "package", + "path": "system.buffers/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.1/System.Buffers.dll", + "lib/netstandard1.1/System.Buffers.xml", + "lib/netstandard2.0/System.Buffers.dll", + "lib/netstandard2.0/System.Buffers.xml", + "lib/uap10.0.16299/_._", + "ref/net45/System.Buffers.dll", + "ref/net45/System.Buffers.xml", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.1/System.Buffers.dll", + "ref/netstandard1.1/System.Buffers.xml", + "ref/netstandard2.0/System.Buffers.dll", + "ref/netstandard2.0/System.Buffers.xml", + "ref/uap10.0.16299/_._", + "system.buffers.4.5.0.nupkg.sha512", + "system.buffers.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.IdentityModel.Tokens.Jwt/6.26.0": { + "sha512": "GT6imbntzCpoGHTRFUa98TPCF9PTnzV1v5KiTj9sT5ZmeYZErNA5ks5VDvYBaOC59y3dQ78IsMzEJm+XrxDk6w==", + "type": "package", + "path": "system.identitymodel.tokens.jwt/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/System.IdentityModel.Tokens.Jwt.dll", + "lib/net45/System.IdentityModel.Tokens.Jwt.xml", + "lib/net461/System.IdentityModel.Tokens.Jwt.dll", + "lib/net461/System.IdentityModel.Tokens.Jwt.xml", + "lib/net472/System.IdentityModel.Tokens.Jwt.dll", + "lib/net472/System.IdentityModel.Tokens.Jwt.xml", + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/net6.0/System.IdentityModel.Tokens.Jwt.xml", + "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.xml", + "system.identitymodel.tokens.jwt.6.26.0.nupkg.sha512", + "system.identitymodel.tokens.jwt.nuspec" + ] + }, + "System.IO/4.3.0": { + "sha512": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "type": "package", + "path": "system.io/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.IO.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.IO.dll", + "ref/netcore50/System.IO.dll", + "ref/netcore50/System.IO.xml", + "ref/netcore50/de/System.IO.xml", + "ref/netcore50/es/System.IO.xml", + "ref/netcore50/fr/System.IO.xml", + "ref/netcore50/it/System.IO.xml", + "ref/netcore50/ja/System.IO.xml", + "ref/netcore50/ko/System.IO.xml", + "ref/netcore50/ru/System.IO.xml", + "ref/netcore50/zh-hans/System.IO.xml", + "ref/netcore50/zh-hant/System.IO.xml", + "ref/netstandard1.0/System.IO.dll", + "ref/netstandard1.0/System.IO.xml", + "ref/netstandard1.0/de/System.IO.xml", + "ref/netstandard1.0/es/System.IO.xml", + "ref/netstandard1.0/fr/System.IO.xml", + "ref/netstandard1.0/it/System.IO.xml", + "ref/netstandard1.0/ja/System.IO.xml", + "ref/netstandard1.0/ko/System.IO.xml", + "ref/netstandard1.0/ru/System.IO.xml", + "ref/netstandard1.0/zh-hans/System.IO.xml", + "ref/netstandard1.0/zh-hant/System.IO.xml", + "ref/netstandard1.3/System.IO.dll", + "ref/netstandard1.3/System.IO.xml", + "ref/netstandard1.3/de/System.IO.xml", + "ref/netstandard1.3/es/System.IO.xml", + "ref/netstandard1.3/fr/System.IO.xml", + "ref/netstandard1.3/it/System.IO.xml", + "ref/netstandard1.3/ja/System.IO.xml", + "ref/netstandard1.3/ko/System.IO.xml", + "ref/netstandard1.3/ru/System.IO.xml", + "ref/netstandard1.3/zh-hans/System.IO.xml", + "ref/netstandard1.3/zh-hant/System.IO.xml", + "ref/netstandard1.5/System.IO.dll", + "ref/netstandard1.5/System.IO.xml", + "ref/netstandard1.5/de/System.IO.xml", + "ref/netstandard1.5/es/System.IO.xml", + "ref/netstandard1.5/fr/System.IO.xml", + "ref/netstandard1.5/it/System.IO.xml", + "ref/netstandard1.5/ja/System.IO.xml", + "ref/netstandard1.5/ko/System.IO.xml", + "ref/netstandard1.5/ru/System.IO.xml", + "ref/netstandard1.5/zh-hans/System.IO.xml", + "ref/netstandard1.5/zh-hant/System.IO.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.io.4.3.0.nupkg.sha512", + "system.io.nuspec" + ] + }, + "System.IO.Pipelines/4.5.2": { + "sha512": "NOC/SO4gSX6t0tB25xxDPqPEzkksuzW7NVFBTQGAkjXXUPQl7ZtyE83T7tUCP2huFBbPombfCKvq1Ox1aG8D9w==", + "type": "package", + "path": "system.io.pipelines/4.5.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/System.IO.Pipelines.dll", + "lib/netcoreapp2.1/System.IO.Pipelines.xml", + "lib/netstandard1.3/System.IO.Pipelines.dll", + "lib/netstandard1.3/System.IO.Pipelines.xml", + "lib/netstandard2.0/System.IO.Pipelines.dll", + "lib/netstandard2.0/System.IO.Pipelines.xml", + "ref/netstandard1.3/System.IO.Pipelines.dll", + "system.io.pipelines.4.5.2.nupkg.sha512", + "system.io.pipelines.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Net.WebSockets.WebSocketProtocol/4.5.1": { + "sha512": "FquLjdb/0CeMqb15u9Px6TwnyFl306WztKWu6sKKc5kWPYMdpi5BFEkdxzGoieYFp9UksyGwJnCw4KKAUfJjrw==", + "type": "package", + "path": "system.net.websockets.websocketprotocol/4.5.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/System.Net.WebSockets.WebSocketProtocol.dll", + "lib/netstandard2.0/System.Net.WebSockets.WebSocketProtocol.dll", + "ref/netstandard2.0/System.Net.WebSockets.WebSocketProtocol.dll", + "system.net.websockets.websocketprotocol.4.5.1.nupkg.sha512", + "system.net.websockets.websocketprotocol.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Reflection/4.3.0": { + "sha512": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "type": "package", + "path": "system.reflection/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Reflection.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Reflection.dll", + "ref/netcore50/System.Reflection.dll", + "ref/netcore50/System.Reflection.xml", + "ref/netcore50/de/System.Reflection.xml", + "ref/netcore50/es/System.Reflection.xml", + "ref/netcore50/fr/System.Reflection.xml", + "ref/netcore50/it/System.Reflection.xml", + "ref/netcore50/ja/System.Reflection.xml", + "ref/netcore50/ko/System.Reflection.xml", + "ref/netcore50/ru/System.Reflection.xml", + "ref/netcore50/zh-hans/System.Reflection.xml", + "ref/netcore50/zh-hant/System.Reflection.xml", + "ref/netstandard1.0/System.Reflection.dll", + "ref/netstandard1.0/System.Reflection.xml", + "ref/netstandard1.0/de/System.Reflection.xml", + "ref/netstandard1.0/es/System.Reflection.xml", + "ref/netstandard1.0/fr/System.Reflection.xml", + "ref/netstandard1.0/it/System.Reflection.xml", + "ref/netstandard1.0/ja/System.Reflection.xml", + "ref/netstandard1.0/ko/System.Reflection.xml", + "ref/netstandard1.0/ru/System.Reflection.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.xml", + "ref/netstandard1.3/System.Reflection.dll", + "ref/netstandard1.3/System.Reflection.xml", + "ref/netstandard1.3/de/System.Reflection.xml", + "ref/netstandard1.3/es/System.Reflection.xml", + "ref/netstandard1.3/fr/System.Reflection.xml", + "ref/netstandard1.3/it/System.Reflection.xml", + "ref/netstandard1.3/ja/System.Reflection.xml", + "ref/netstandard1.3/ko/System.Reflection.xml", + "ref/netstandard1.3/ru/System.Reflection.xml", + "ref/netstandard1.3/zh-hans/System.Reflection.xml", + "ref/netstandard1.3/zh-hant/System.Reflection.xml", + "ref/netstandard1.5/System.Reflection.dll", + "ref/netstandard1.5/System.Reflection.xml", + "ref/netstandard1.5/de/System.Reflection.xml", + "ref/netstandard1.5/es/System.Reflection.xml", + "ref/netstandard1.5/fr/System.Reflection.xml", + "ref/netstandard1.5/it/System.Reflection.xml", + "ref/netstandard1.5/ja/System.Reflection.xml", + "ref/netstandard1.5/ko/System.Reflection.xml", + "ref/netstandard1.5/ru/System.Reflection.xml", + "ref/netstandard1.5/zh-hans/System.Reflection.xml", + "ref/netstandard1.5/zh-hant/System.Reflection.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.reflection.4.3.0.nupkg.sha512", + "system.reflection.nuspec" + ] + }, + "System.Reflection.Emit/4.3.0": { + "sha512": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "type": "package", + "path": "system.reflection.emit/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/monotouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.dll", + "lib/netstandard1.3/System.Reflection.Emit.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/net45/_._", + "ref/netstandard1.1/System.Reflection.Emit.dll", + "ref/netstandard1.1/System.Reflection.Emit.xml", + "ref/netstandard1.1/de/System.Reflection.Emit.xml", + "ref/netstandard1.1/es/System.Reflection.Emit.xml", + "ref/netstandard1.1/fr/System.Reflection.Emit.xml", + "ref/netstandard1.1/it/System.Reflection.Emit.xml", + "ref/netstandard1.1/ja/System.Reflection.Emit.xml", + "ref/netstandard1.1/ko/System.Reflection.Emit.xml", + "ref/netstandard1.1/ru/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hans/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hant/System.Reflection.Emit.xml", + "ref/xamarinmac20/_._", + "system.reflection.emit.4.3.0.nupkg.sha512", + "system.reflection.emit.nuspec" + ] + }, + "System.Reflection.Emit.ILGeneration/4.3.0": { + "sha512": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "type": "package", + "path": "system.reflection.emit.ilgeneration/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.ILGeneration.dll", + "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll", + "lib/portable-net45+wp8/_._", + "lib/wp80/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.dll", + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/de/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/es/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/fr/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/it/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ja/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ko/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ru/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Emit.ILGeneration.xml", + "ref/portable-net45+wp8/_._", + "ref/wp80/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/_._", + "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512", + "system.reflection.emit.ilgeneration.nuspec" + ] + }, + "System.Reflection.Primitives/4.3.0": { + "sha512": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "type": "package", + "path": "system.reflection.primitives/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Primitives.dll", + "ref/netcore50/System.Reflection.Primitives.xml", + "ref/netcore50/de/System.Reflection.Primitives.xml", + "ref/netcore50/es/System.Reflection.Primitives.xml", + "ref/netcore50/fr/System.Reflection.Primitives.xml", + "ref/netcore50/it/System.Reflection.Primitives.xml", + "ref/netcore50/ja/System.Reflection.Primitives.xml", + "ref/netcore50/ko/System.Reflection.Primitives.xml", + "ref/netcore50/ru/System.Reflection.Primitives.xml", + "ref/netcore50/zh-hans/System.Reflection.Primitives.xml", + "ref/netcore50/zh-hant/System.Reflection.Primitives.xml", + "ref/netstandard1.0/System.Reflection.Primitives.dll", + "ref/netstandard1.0/System.Reflection.Primitives.xml", + "ref/netstandard1.0/de/System.Reflection.Primitives.xml", + "ref/netstandard1.0/es/System.Reflection.Primitives.xml", + "ref/netstandard1.0/fr/System.Reflection.Primitives.xml", + "ref/netstandard1.0/it/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ja/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ko/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ru/System.Reflection.Primitives.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Primitives.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Primitives.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.reflection.primitives.4.3.0.nupkg.sha512", + "system.reflection.primitives.nuspec" + ] + }, + "System.Runtime/4.3.0": { + "sha512": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "type": "package", + "path": "system.runtime/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Runtime.dll", + "lib/portable-net45+win8+wp80+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Runtime.dll", + "ref/netcore50/System.Runtime.dll", + "ref/netcore50/System.Runtime.xml", + "ref/netcore50/de/System.Runtime.xml", + "ref/netcore50/es/System.Runtime.xml", + "ref/netcore50/fr/System.Runtime.xml", + "ref/netcore50/it/System.Runtime.xml", + "ref/netcore50/ja/System.Runtime.xml", + "ref/netcore50/ko/System.Runtime.xml", + "ref/netcore50/ru/System.Runtime.xml", + "ref/netcore50/zh-hans/System.Runtime.xml", + "ref/netcore50/zh-hant/System.Runtime.xml", + "ref/netstandard1.0/System.Runtime.dll", + "ref/netstandard1.0/System.Runtime.xml", + "ref/netstandard1.0/de/System.Runtime.xml", + "ref/netstandard1.0/es/System.Runtime.xml", + "ref/netstandard1.0/fr/System.Runtime.xml", + "ref/netstandard1.0/it/System.Runtime.xml", + "ref/netstandard1.0/ja/System.Runtime.xml", + "ref/netstandard1.0/ko/System.Runtime.xml", + "ref/netstandard1.0/ru/System.Runtime.xml", + "ref/netstandard1.0/zh-hans/System.Runtime.xml", + "ref/netstandard1.0/zh-hant/System.Runtime.xml", + "ref/netstandard1.2/System.Runtime.dll", + "ref/netstandard1.2/System.Runtime.xml", + "ref/netstandard1.2/de/System.Runtime.xml", + "ref/netstandard1.2/es/System.Runtime.xml", + "ref/netstandard1.2/fr/System.Runtime.xml", + "ref/netstandard1.2/it/System.Runtime.xml", + "ref/netstandard1.2/ja/System.Runtime.xml", + "ref/netstandard1.2/ko/System.Runtime.xml", + "ref/netstandard1.2/ru/System.Runtime.xml", + "ref/netstandard1.2/zh-hans/System.Runtime.xml", + "ref/netstandard1.2/zh-hant/System.Runtime.xml", + "ref/netstandard1.3/System.Runtime.dll", + "ref/netstandard1.3/System.Runtime.xml", + "ref/netstandard1.3/de/System.Runtime.xml", + "ref/netstandard1.3/es/System.Runtime.xml", + "ref/netstandard1.3/fr/System.Runtime.xml", + "ref/netstandard1.3/it/System.Runtime.xml", + "ref/netstandard1.3/ja/System.Runtime.xml", + "ref/netstandard1.3/ko/System.Runtime.xml", + "ref/netstandard1.3/ru/System.Runtime.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.xml", + "ref/netstandard1.5/System.Runtime.dll", + "ref/netstandard1.5/System.Runtime.xml", + "ref/netstandard1.5/de/System.Runtime.xml", + "ref/netstandard1.5/es/System.Runtime.xml", + "ref/netstandard1.5/fr/System.Runtime.xml", + "ref/netstandard1.5/it/System.Runtime.xml", + "ref/netstandard1.5/ja/System.Runtime.xml", + "ref/netstandard1.5/ko/System.Runtime.xml", + "ref/netstandard1.5/ru/System.Runtime.xml", + "ref/netstandard1.5/zh-hans/System.Runtime.xml", + "ref/netstandard1.5/zh-hant/System.Runtime.xml", + "ref/portable-net45+win8+wp80+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.runtime.4.3.0.nupkg.sha512", + "system.runtime.nuspec" + ] + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "sha512": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "type": "package", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net461/System.Runtime.CompilerServices.Unsafe.xml", + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml", + "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", + "system.runtime.compilerservices.unsafe.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Security.Cryptography.Cng/4.5.0": { + "sha512": "WG3r7EyjUe9CMPFSs6bty5doUqT+q9pbI80hlNzo2SkPkZ4VTuZkGWjpp77JB8+uaL4DFPRdBsAY+DX3dBK92A==", + "type": "package", + "path": "system.security.cryptography.cng/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.Cng.dll", + "lib/net461/System.Security.Cryptography.Cng.dll", + "lib/net462/System.Security.Cryptography.Cng.dll", + "lib/net47/System.Security.Cryptography.Cng.dll", + "lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll", + "lib/netstandard1.3/System.Security.Cryptography.Cng.dll", + "lib/netstandard1.4/System.Security.Cryptography.Cng.dll", + "lib/netstandard1.6/System.Security.Cryptography.Cng.dll", + "lib/netstandard2.0/System.Security.Cryptography.Cng.dll", + "lib/uap10.0.16299/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.Cng.dll", + "ref/net461/System.Security.Cryptography.Cng.dll", + "ref/net461/System.Security.Cryptography.Cng.xml", + "ref/net462/System.Security.Cryptography.Cng.dll", + "ref/net462/System.Security.Cryptography.Cng.xml", + "ref/net47/System.Security.Cryptography.Cng.dll", + "ref/net47/System.Security.Cryptography.Cng.xml", + "ref/netcoreapp2.0/System.Security.Cryptography.Cng.dll", + "ref/netcoreapp2.0/System.Security.Cryptography.Cng.xml", + "ref/netcoreapp2.1/System.Security.Cryptography.Cng.dll", + "ref/netcoreapp2.1/System.Security.Cryptography.Cng.xml", + "ref/netstandard1.3/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.4/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.6/System.Security.Cryptography.Cng.dll", + "ref/netstandard2.0/System.Security.Cryptography.Cng.dll", + "ref/netstandard2.0/System.Security.Cryptography.Cng.xml", + "ref/uap10.0.16299/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/win/lib/net46/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net461/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net462/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net47/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netcoreapp2.0/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netstandard1.4/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/uap10.0.16299/_._", + "system.security.cryptography.cng.4.5.0.nupkg.sha512", + "system.security.cryptography.cng.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Security.Principal.Windows/4.5.0": { + "sha512": "U77HfRXlZlOeIXd//Yoj6Jnk8AXlbeisf1oq1os+hxOGVnuG+lGSfGqTwTZBoORFF6j/0q7HXIl8cqwQ9aUGqQ==", + "type": "package", + "path": "system.security.principal.windows/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net46/System.Security.Principal.Windows.dll", + "lib/net461/System.Security.Principal.Windows.dll", + "lib/netstandard1.3/System.Security.Principal.Windows.dll", + "lib/netstandard2.0/System.Security.Principal.Windows.dll", + "lib/uap10.0.16299/_._", + "ref/net46/System.Security.Principal.Windows.dll", + "ref/net461/System.Security.Principal.Windows.dll", + "ref/net461/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/System.Security.Principal.Windows.dll", + "ref/netstandard1.3/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/de/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/es/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/fr/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/it/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/ja/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/ko/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/ru/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/zh-hans/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/zh-hant/System.Security.Principal.Windows.xml", + "ref/netstandard2.0/System.Security.Principal.Windows.dll", + "ref/netstandard2.0/System.Security.Principal.Windows.xml", + "ref/uap10.0.16299/_._", + "runtimes/unix/lib/netcoreapp2.0/System.Security.Principal.Windows.dll", + "runtimes/win/lib/net46/System.Security.Principal.Windows.dll", + "runtimes/win/lib/net461/System.Security.Principal.Windows.dll", + "runtimes/win/lib/netcoreapp2.0/System.Security.Principal.Windows.dll", + "runtimes/win/lib/netstandard1.3/System.Security.Principal.Windows.dll", + "runtimes/win/lib/uap10.0.16299/_._", + "system.security.principal.windows.4.5.0.nupkg.sha512", + "system.security.principal.windows.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Text.Encoding/4.3.0": { + "sha512": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "type": "package", + "path": "system.text.encoding/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Text.Encoding.dll", + "ref/netcore50/System.Text.Encoding.xml", + "ref/netcore50/de/System.Text.Encoding.xml", + "ref/netcore50/es/System.Text.Encoding.xml", + "ref/netcore50/fr/System.Text.Encoding.xml", + "ref/netcore50/it/System.Text.Encoding.xml", + "ref/netcore50/ja/System.Text.Encoding.xml", + "ref/netcore50/ko/System.Text.Encoding.xml", + "ref/netcore50/ru/System.Text.Encoding.xml", + "ref/netcore50/zh-hans/System.Text.Encoding.xml", + "ref/netcore50/zh-hant/System.Text.Encoding.xml", + "ref/netstandard1.0/System.Text.Encoding.dll", + "ref/netstandard1.0/System.Text.Encoding.xml", + "ref/netstandard1.0/de/System.Text.Encoding.xml", + "ref/netstandard1.0/es/System.Text.Encoding.xml", + "ref/netstandard1.0/fr/System.Text.Encoding.xml", + "ref/netstandard1.0/it/System.Text.Encoding.xml", + "ref/netstandard1.0/ja/System.Text.Encoding.xml", + "ref/netstandard1.0/ko/System.Text.Encoding.xml", + "ref/netstandard1.0/ru/System.Text.Encoding.xml", + "ref/netstandard1.0/zh-hans/System.Text.Encoding.xml", + "ref/netstandard1.0/zh-hant/System.Text.Encoding.xml", + "ref/netstandard1.3/System.Text.Encoding.dll", + "ref/netstandard1.3/System.Text.Encoding.xml", + "ref/netstandard1.3/de/System.Text.Encoding.xml", + "ref/netstandard1.3/es/System.Text.Encoding.xml", + "ref/netstandard1.3/fr/System.Text.Encoding.xml", + "ref/netstandard1.3/it/System.Text.Encoding.xml", + "ref/netstandard1.3/ja/System.Text.Encoding.xml", + "ref/netstandard1.3/ko/System.Text.Encoding.xml", + "ref/netstandard1.3/ru/System.Text.Encoding.xml", + "ref/netstandard1.3/zh-hans/System.Text.Encoding.xml", + "ref/netstandard1.3/zh-hant/System.Text.Encoding.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.text.encoding.4.3.0.nupkg.sha512", + "system.text.encoding.nuspec" + ] + }, + "System.Text.Encodings.Web/4.7.2": { + "sha512": "iTUgB/WtrZ1sWZs84F2hwyQhiRH6QNjQv2DkwrH+WP6RoFga2Q1m3f9/Q7FG8cck8AdHitQkmkXSY8qylcDmuA==", + "type": "package", + "path": "system.text.encodings.web/4.7.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.Text.Encodings.Web.dll", + "lib/net461/System.Text.Encodings.Web.xml", + "lib/netstandard1.0/System.Text.Encodings.Web.dll", + "lib/netstandard1.0/System.Text.Encodings.Web.xml", + "lib/netstandard2.0/System.Text.Encodings.Web.dll", + "lib/netstandard2.0/System.Text.Encodings.Web.xml", + "lib/netstandard2.1/System.Text.Encodings.Web.dll", + "lib/netstandard2.1/System.Text.Encodings.Web.xml", + "system.text.encodings.web.4.7.2.nupkg.sha512", + "system.text.encodings.web.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Text.Json/4.7.2": { + "sha512": "TcMd95wcrubm9nHvJEQs70rC0H/8omiSGGpU4FQ/ZA1URIqD4pjmFJh2Mfv1yH1eHgJDWTi2hMDXwTET+zOOyg==", + "type": "package", + "path": "system.text.json/4.7.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.Text.Json.dll", + "lib/net461/System.Text.Json.xml", + "lib/netcoreapp3.0/System.Text.Json.dll", + "lib/netcoreapp3.0/System.Text.Json.xml", + "lib/netstandard2.0/System.Text.Json.dll", + "lib/netstandard2.0/System.Text.Json.xml", + "system.text.json.4.7.2.nupkg.sha512", + "system.text.json.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Threading.Channels/4.5.0": { + "sha512": "MEH06N0rIGmRT4LOKQ2BmUO0IxfvmIY/PaouSq+DFQku72OL8cxfw8W99uGpTCFf2vx2QHLRSh374iSM3asdTA==", + "type": "package", + "path": "system.threading.channels/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/System.Threading.Channels.dll", + "lib/netcoreapp2.1/System.Threading.Channels.xml", + "lib/netstandard1.3/System.Threading.Channels.dll", + "lib/netstandard1.3/System.Threading.Channels.xml", + "lib/netstandard2.0/System.Threading.Channels.dll", + "lib/netstandard2.0/System.Threading.Channels.xml", + "system.threading.channels.4.5.0.nupkg.sha512", + "system.threading.channels.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Threading.Tasks/4.3.0": { + "sha512": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "type": "package", + "path": "system.threading.tasks/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Threading.Tasks.dll", + "ref/netcore50/System.Threading.Tasks.xml", + "ref/netcore50/de/System.Threading.Tasks.xml", + "ref/netcore50/es/System.Threading.Tasks.xml", + "ref/netcore50/fr/System.Threading.Tasks.xml", + "ref/netcore50/it/System.Threading.Tasks.xml", + "ref/netcore50/ja/System.Threading.Tasks.xml", + "ref/netcore50/ko/System.Threading.Tasks.xml", + "ref/netcore50/ru/System.Threading.Tasks.xml", + "ref/netcore50/zh-hans/System.Threading.Tasks.xml", + "ref/netcore50/zh-hant/System.Threading.Tasks.xml", + "ref/netstandard1.0/System.Threading.Tasks.dll", + "ref/netstandard1.0/System.Threading.Tasks.xml", + "ref/netstandard1.0/de/System.Threading.Tasks.xml", + "ref/netstandard1.0/es/System.Threading.Tasks.xml", + "ref/netstandard1.0/fr/System.Threading.Tasks.xml", + "ref/netstandard1.0/it/System.Threading.Tasks.xml", + "ref/netstandard1.0/ja/System.Threading.Tasks.xml", + "ref/netstandard1.0/ko/System.Threading.Tasks.xml", + "ref/netstandard1.0/ru/System.Threading.Tasks.xml", + "ref/netstandard1.0/zh-hans/System.Threading.Tasks.xml", + "ref/netstandard1.0/zh-hant/System.Threading.Tasks.xml", + "ref/netstandard1.3/System.Threading.Tasks.dll", + "ref/netstandard1.3/System.Threading.Tasks.xml", + "ref/netstandard1.3/de/System.Threading.Tasks.xml", + "ref/netstandard1.3/es/System.Threading.Tasks.xml", + "ref/netstandard1.3/fr/System.Threading.Tasks.xml", + "ref/netstandard1.3/it/System.Threading.Tasks.xml", + "ref/netstandard1.3/ja/System.Threading.Tasks.xml", + "ref/netstandard1.3/ko/System.Threading.Tasks.xml", + "ref/netstandard1.3/ru/System.Threading.Tasks.xml", + "ref/netstandard1.3/zh-hans/System.Threading.Tasks.xml", + "ref/netstandard1.3/zh-hant/System.Threading.Tasks.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.threading.tasks.4.3.0.nupkg.sha512", + "system.threading.tasks.nuspec" + ] + }, "Haoliang.Models/1.0.0": { "type": "project", "path": "../Haoliang.Models/Haoliang.Models.csproj", @@ -23,7 +2312,14 @@ }, "projectFileDependencyGroups": { "net6.0": [ - "Haoliang.Models >= 1.0.0" + "BCrypt.Net-Next >= 4.0.3", + "Haoliang.Models >= 1.0.0", + "Microsoft.AspNetCore.Http.Abstractions >= 2.2.0", + "Microsoft.AspNetCore.SignalR >= 1.1.0", + "Microsoft.Extensions.Caching.Memory >= 6.0.0", + "Microsoft.Extensions.Logging.Abstractions >= 6.0.0", + "Microsoft.IdentityModel.Tokens >= 6.26.0", + "System.IdentityModel.Tokens.Jwt >= 6.26.0" ] }, "packageFolders": { @@ -66,6 +2362,36 @@ "frameworks": { "net6.0": { "targetAlias": "net6.0", + "dependencies": { + "BCrypt.Net-Next": { + "target": "Package", + "version": "[4.0.3, )" + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "target": "Package", + "version": "[2.2.0, )" + }, + "Microsoft.AspNetCore.SignalR": { + "target": "Package", + "version": "[1.1.0, )" + }, + "Microsoft.Extensions.Caching.Memory": { + "target": "Package", + "version": "[6.0.0, )" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "target": "Package", + "version": "[6.0.0, )" + }, + "Microsoft.IdentityModel.Tokens": { + "target": "Package", + "version": "[6.26.0, )" + }, + "System.IdentityModel.Tokens.Jwt": { + "target": "Package", + "version": "[6.26.0, )" + } + }, "imports": [ "net461", "net462", diff --git a/Haoliang.Core/obj/project.nuget.cache b/Haoliang.Core/obj/project.nuget.cache index cfc1c7c..948185e 100644 --- a/Haoliang.Core/obj/project.nuget.cache +++ b/Haoliang.Core/obj/project.nuget.cache @@ -1,8 +1,67 @@ { "version": 2, - "dgSpecHash": "cpjVOrge9oP4blMToOuinjxsDfSIOVlA2diiL86O/46lzbrVMwXVO6aHngOLfhhrjE+F6pXn2HKY1Qy+Vx4fhg==", + "dgSpecHash": "JXZHRpHRIVB4k9X9GkQYjgJFTdFXJDSb+42sJl8bI1k2k9teZWdbTGwx0JJgh5J3M4+9rp1wh+omg4LmNZnqzQ==", "success": true, "projectFilePath": "/root/opencode/haoliang/Haoliang.Core/Haoliang.Core.csproj", - "expectedPackageFiles": [], + "expectedPackageFiles": [ + "/root/.nuget/packages/bcrypt.net-next/4.0.3/bcrypt.net-next.4.0.3.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.aspnetcore.authorization/2.2.0/microsoft.aspnetcore.authorization.2.2.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.aspnetcore.connections.abstractions/2.2.0/microsoft.aspnetcore.connections.abstractions.2.2.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.aspnetcore.hosting.server.abstractions/2.2.0/microsoft.aspnetcore.hosting.server.abstractions.2.2.0.nupkg.sha512", + "/root/.nuget/packages/microsoft.aspnetcore.http/2.2.0/microsoft.aspnetcore.http.2.2.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.aspnetcore.http.connections/1.1.0/microsoft.aspnetcore.http.connections.1.1.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.aspnetcore.http.extensions/2.2.0/microsoft.aspnetcore.http.extensions.2.2.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.aspnetcore.routing/2.2.0/microsoft.aspnetcore.routing.2.2.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.aspnetcore.signalr/1.1.0/microsoft.aspnetcore.signalr.1.1.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/microsoft.aspnetcore.signalr.core/1.1.0/microsoft.aspnetcore.signalr.core.1.1.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/microsoft.aspnetcore.websockets/2.2.0/microsoft.aspnetcore.websockets.2.2.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.extensions.caching.abstractions/6.0.0/microsoft.extensions.caching.abstractions.6.0.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.extensions.configuration.abstractions/2.2.0/microsoft.extensions.configuration.abstractions.2.2.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.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.abstractions/6.0.0/microsoft.extensions.logging.abstractions.6.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/6.0.0/microsoft.extensions.options.6.0.0.nupkg.sha512", + "/root/.nuget/packages/microsoft.extensions.primitives/6.0.0/microsoft.extensions.primitives.6.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/newtonsoft.json/11.0.2/newtonsoft.json.11.0.2.nupkg.sha512", + "/root/.nuget/packages/system.buffers/4.5.0/system.buffers.4.5.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/4.7.2/system.text.encodings.web.4.7.2.nupkg.sha512", + "/root/.nuget/packages/system.text.json/4.7.2/system.text.json.4.7.2.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": [] } \ No newline at end of file diff --git a/Haoliang.Data/Entities/CNCBusinessDbContext.cs b/Haoliang.Data/Entities/CNCBusinessDbContext.cs new file mode 100644 index 0000000..bafb88a --- /dev/null +++ b/Haoliang.Data/Entities/CNCBusinessDbContext.cs @@ -0,0 +1,437 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Haoliang.Data.Entities; + +namespace Haoliang.Data +{ + public class CNCBusinessDbContext : DbContext + { + public CNCBusinessDbContext(DbContextOptions options) : base(options) { } + + // Device Management + public DbSet Devices { get; set; } + public DbSet DeviceStatus { get; set; } + public DbSet DeviceCurrentStatus { get; set; } + public DbSet TagData { get; set; } + + // Template Management + public DbSet CNCTemplates { get; set; } + public DbSet TagMappings { get; set; } + + // Production Management + public DbSet ProductionRecords { get; set; } + public DbSet ProgramProductionSummary { get; set; } + public DbSet ProductionSummaries { get; set; } + + // User Management + public DbSet Users { get; set; } + public DbSet Roles { get; set; } + public DbSet Employees { get; set; } + public DbSet UserPermissions { get; set; } + public DbSet RolePermissions { get; set; } + public DbSet UserSessions { get; set; } + public DbSet PasswordResets { get; set; } + + // System Management + public DbSet Alarms { get; set; } + public DbSet AlarmRules { get; set; } + public DbSet AlarmNotifications { get; set; } + public DbSet SystemConfigs { get; set; } + public DbSet LogEntries { get; set; } + public DbSet StatisticRules { get; set; } + public DbSet StatisticResults { get; set; } + + // Data Collection + public DbSet CollectionTasks { get; set; } + public DbSet CollectionResults { get; set; } + public DbSet CollectionLogs { get; set; } + public DbSet CollectionConfigs { get; set; } + + // Scheduled Tasks + public DbSet ScheduledTasks { get; set; } + public DbSet TaskExecutionResults { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Configure MySQL-specific settings + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + // Set default charset and collation + if (typeof(BaseEntity).IsAssignableFrom(entityType.ClrType)) + { + modelBuilder.Entity(entityType.ClrType) + .ToTable(entityType.GetTableName() ?? "", t => t + .charset("utf8mb4") + .collation("utf8mb4_unicode_ci")); + } + } + + // Configure relationships + ConfigureDeviceRelationships(modelBuilder); + ConfigureUserRelationships(modelBuilder); + ConfigureAlarmRelationships(modelBuilder); + ConfigureCollectionRelationships(modelBuilder); + ConfigureProductionRelationships(modelBuilder); + ConfigureTemplateRelationships(modelBuilder); + + // Configure indexes + ConfigureIndexes(modelBuilder); + + // Configure constraints + ConfigureConstraints(modelBuilder); + + // Configure data conversions + ConfigureDataConversions(modelBuilder); + } + + private void ConfigureDeviceRelationships(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(d => d.DeviceStatus) + .WithOne() + .HasForeignKey(ds => ds.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(d => d.CollectionResults) + .WithOne() + .HasForeignKey(cr => cr.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(d => d.CollectionLogs) + .WithOne() + .HasForeignKey(cl => cl.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(d => d.ProductionRecords) + .WithOne() + .HasForeignKey(pr => pr.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + } + + private void ConfigureUserRelationships(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasOne(u => u.Role) + .WithMany() + .HasForeignKey(u => u.RoleId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasMany(u => u.UserPermissions) + .WithOne(up => up.User) + .HasForeignKey(up => up.UserId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.UserSessions) + .WithOne(us => us.User) + .HasForeignKey(us => us.UserId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.PasswordResets) + .WithOne(pr => pr.User) + .HasForeignKey(pr => pr.UserId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(r => r.RolePermissions) + .WithOne(rp => rp.Role) + .HasForeignKey(rp => rp.RoleId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(e => e.DeviceAssignments) + .WithOne(da => da.Employee) + .HasForeignKey(da => da.EmployeeId) + .OnDelete(DeleteBehavior.Cascade); + } + + private void ConfigureAlarmRelationships(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(a => a.AlarmNotifications) + .WithOne(an => an.Alarm) + .HasForeignKey(an => an.AlarmId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(ar => ar.Alarms) + .WithOne(a => a.AlarmRule) + .HasForeignKey(a => a.AlarmRuleId) + .OnDelete(DeleteBehavior.Restrict); + } + + private void ConfigureCollectionRelationships(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(ct => ct.CollectionResults) + .WithOne(cr => cr.CollectionTask) + .HasForeignKey(cr => cr.TaskId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(cr => cr.CollectionLogs) + .WithOne(cl => cl.CollectionResult) + .HasForeignKey(cl => cl.ResultId) + .OnDelete(DeleteBehavior.Cascade); + } + + private void ConfigureProductionRelationships(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(pr => pr.ProgramSummaries) + .WithOne(pps => pps.ProductionRecord) + .HasForeignKey(pps => pps.RecordId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(d => d.ProductionSummaries) + .WithOne(ps => ps.Device) + .HasForeignKey(ps => ps.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + } + + private void ConfigureTemplateRelationships(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(t => t.TagMappings) + .WithOne(tm => tm.Template) + .HasForeignKey(tm => tm.TemplateId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(t => t.Devices) + .WithOne(d => d.Template) + .HasForeignKey(d => d.TemplateId) + .OnDelete(DeleteBehavior.Restrict); + } + + private void ConfigureIndexes(ModelBuilder modelBuilder) + { + // Device indexes + modelBuilder.Entity() + .HasIndex(d => d.DeviceCode) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(d => d.IPAddress); + + modelBuilder.Entity() + .HasIndex(d => d.IsOnline); + + modelBuilder.Entity() + .HasIndex(d => d.IsAvailable); + + // User indexes + modelBuilder.Entity() + .HasIndex(u => u.Username) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(u => u.Email) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(u => u.IsActive); + + // Alarm indexes + modelBuilder.Entity() + .HasIndex(a => a.AlarmStatus); + + modelBuilder.Entity() + .HasIndex(a => a.IsActive); + + modelBuilder.Entity() + .HasIndex(a => a.CreateTime); + + // Collection indexes + modelBuilder.Entity() + .HasIndex(cr => cr.DeviceId); + + modelBuilder.Entity() + .HasIndex(cr => cr.CollectionTime); + + modelBuilder.Entity() + .HasIndex(cr => cr.IsSuccess); + + // Production indexes + modelBuilder.Entity() + .HasIndex(pr => pr.DeviceId); + + modelBuilder.Entity() + .HasIndex(pr => pr.ProductionDate); + + modelBuilder.Entity() + .HasIndex(pr => pr.IsCompleted); + + // Template indexes + modelBuilder.Entity() + .HasIndex(t => t.BrandName); + + modelBuilder.Entity() + .HasIndex(t => t.IsActive); + + // System config indexes + modelBuilder.Entity() + .HasIndex(sc => sc.ConfigKey) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(sc => sc.Category); + + modelBuilder.Entity() + .HasIndex(sc => sc.IsActive); + } + + private void ConfigureConstraints(ModelBuilder modelBuilder) + { + // Device constraints + modelBuilder.Entity() + .Property(d => d.DeviceCode) + .IsRequired() + .HasMaxLength(50); + + modelBuilder.Entity() + .Property(d => d.DeviceName) + .IsRequired() + .HasMaxLength(100); + + modelBuilder.Entity() + .Property(d => d.IPAddress) + .IsRequired() + .HasMaxLength(15); + + modelBuilder.Entity() + .Property(d => d.HttpUrl) + .IsRequired() + .HasMaxLength(255); + + modelBuilder.Entity() + .Property(d => d.CollectionInterval) + .IsRequired(); + + // User constraints + modelBuilder.Entity() + .Property(u => u.Username) + .IsRequired() + .HasMaxLength(50); + + modelBuilder.Entity() + .Property(u => u.Email) + .IsRequired() + .HasMaxLength(100); + + modelBuilder.Entity() + .Property(u => u.PasswordHash) + .IsRequired() + .HasMaxLength(255); + + modelBuilder.Entity() + .Property(u => u.FirstName) + .IsRequired() + .HasMaxLength(50); + + modelBuilder.Entity() + .Property(u => u.LastName) + .IsRequired() + .HasMaxLength(50); + + // Alarm constraints + modelBuilder.Entity() + .Property(a => a.AlarmType) + .IsRequired() + .HasMaxLength(50); + + modelBuilder.Entity() + .Property(a => a.Title) + .IsRequired() + .HasMaxLength(255); + + modelBuilder.Entity() + .Property(a => a.AlarmStatus) + .IsRequired(); + + // System config constraints + modelBuilder.Entity() + .Property(sc => sc.ConfigKey) + .IsRequired() + .HasMaxLength(100); + + modelBuilder.Entity() + .Property(sc => sc.ConfigValue) + .IsRequired(); + + modelBuilder.Entity() + .Property(sc => sc.Category) + .IsRequired() + .HasMaxLength(50); + } + + private void ConfigureDataConversions(ModelBuilder modelBuilder) + { + // Configure decimal properties with appropriate precision + modelBuilder.Entity() + .Property(ps => ps.QualityRate) + .HasColumnType("decimal(5,2)"); + + modelBuilder.Entity() + .Property(sc => sc.ConfigValue) + .HasColumnType("text"); + + modelBuilder.Entity() + .Property(cl => cl.LogData) + .HasColumnType("text"); + + modelBuilder.Entity() + .Property(cr => cr.RawData) + .HasColumnType("text"); + + modelBuilder.Entity() + .Property(cr => cr.ParsedData) + .HasColumnType("text"); + + modelBuilder.Entity() + .Property(t => t.TagsJson) + .HasColumnType("json"); + + modelBuilder.Entity() + .Property(t => t.DataProcessingRulesJson) + .HasColumnType("json"); + + modelBuilder.Entity() + .Property(sr => sr.ResultData) + .HasColumnType("json"); + } + + public static void ConfigureDatabaseServices(IServiceCollection services, IConfiguration configuration) + { + var connectionString = configuration.GetConnectionString("CNCBusinessDb"); + + services.AddDbContext(options => + { + options.UseMySql(connectionString, + mysqlOptions => + { + mysqlOptions.EnableRetryOnFailure( + maxRetryCount: 3, + maxRetryDelay: TimeSpan.FromSeconds(30), + errorNumbersToAdd: null); + mysqlOptions.EnableSensitiveDataLogging(true); + }); + + // Enable lazy loading for development + if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") + { + options.EnableSensitiveDataLogging(); + options.EnableDetailedErrors(); + } + }); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Haoliang.Data.csproj b/Haoliang.Data/Haoliang.Data.csproj index a519433..3a7548b 100644 --- a/Haoliang.Data/Haoliang.Data.csproj +++ b/Haoliang.Data/Haoliang.Data.csproj @@ -2,27 +2,16 @@ - - - - + + + - - - - - - - - - - - + net6.0 diff --git a/Haoliang.Data/Migrations/20240101120000_InitialCreate.cs b/Haoliang.Data/Migrations/20240101120000_InitialCreate.cs new file mode 100644 index 0000000..d9b4f9d --- /dev/null +++ b/Haoliang.Data/Migrations/20240101120000_InitialCreate.cs @@ -0,0 +1,1011 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Haoliang.Data.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Alarms", + columns: table => new + { + AlarmId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: true), + DeviceCode = table.Column(type: "varchar(100)", nullable: true), + AlarmType = table.Column(type: "varchar(50)", nullable: false), + AlarmSeverity = table.Column(type: "int", nullable: false), + Title = table.Column(type: "varchar(255)", nullable: false), + Description = table.Column(type: "text", nullable: true), + AlarmStatus = table.Column(type: "int", nullable: false), + CreateTime = table.Column(type: "datetime", nullable: false), + AcknowledgedTime = table.Column(type: "datetime", nullable: true), + ResolvedTime = table.Column(type: "datetime", nullable: true), + AcknowledgeNote = table.Column(type: "text", nullable: true), + ResolutionNote = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Alarms", x => x.AlarmId); + }); + + migrationBuilder.CreateTable( + name: "AlarmNotifications", + columns: table => new + { + NotificationId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + AlarmId = table.Column(type: "int", nullable: false), + NotificationType = table.Column(type: "int", nullable: false), + Recipient = table.Column(type: "varchar(255)", nullable: false), + Message = table.Column(type: "text", nullable: false), + Status = table.Column(type: "int", nullable: false), + SendTime = table.Column(type: "datetime", nullable: false), + ErrorMessage = table.Column(type: "text", nullable: true), + RetryTime = table.Column(type: "datetime", nullable: true), + RetryCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AlarmNotifications", x => x.NotificationId); + table.ForeignKey( + name: "FK_AlarmNotifications_Alarms", + column: x => x.AlarmId, + principalTable: "Alarms", + principalColumn: "AlarmId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AlarmRules", + columns: table => new + { + RuleId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + RuleName = table.Column(type: "varchar(100)", nullable: false), + DeviceId = table.Column(type: "int", nullable: true), + AlarmType = table.Column(type: "varchar(50)", nullable: false), + Condition = table.Column(type: "text", nullable: false), + Threshold = table.Column(type: "varchar(100)", nullable: true), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AlarmRules", x => x.RuleId); + }); + + migrationBuilder.CreateTable( + name: "CollectionConfigs", + columns: table => new + { + ConfigId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + CollectionInterval = table.Column(type: "int", nullable: false), + TimeoutSeconds = table.Column(type: "int", nullable: false), + RetryAttempts = table.Column(type: "int", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionConfigs", x => x.ConfigId); + }); + + migrationBuilder.CreateTable( + name: "CollectionLogs", + columns: table => new + { + LogId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + LogLevel = table.Column(type: "int", nullable: false), + LogCategory = table.Column(type: "varchar(50)", nullable: false), + LogMessage = table.Column(type: "text", nullable: false), + LogData = table.Column(type: "text", nullable: true), + LogTime = table.Column(type: "datetime", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionLogs", x => x.LogId); + }); + + migrationBuilder.CreateTable( + name: "CollectionResults", + columns: table => new + { + ResultId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + IsSuccess = table.Column(type: "tinyint(1)", nullable: false), + RawData = table.Column(type: "text", nullable: true), + ParsedData = table.Column(type: "text", nullable: true), + CollectionTime = table.Column(type: "datetime", nullable: false), + ResponseTimeMs = table.Column(type: "int", nullable: true), + DataSize = table.Column(type: "int", nullable: true), + ErrorMessage = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionResults", x => x.ResultId); + }); + + migrationBuilder.CreateTable( + name: "CollectionTasks", + columns: table => new + { + TaskId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + TaskName = table.Column(type: "varchar(100)", nullable: false), + Status = table.Column(type: "varchar(20)", nullable: false), + ScheduledTime = table.Column(type: "datetime", nullable: false), + StartTime = table.Column(type: "datetime", nullable: true), + EndTime = table.Column(type: "datetime", nullable: true), + IsSuccess = table.Column(type: "tinyint(1)", nullable: false), + ErrorMessage = table.Column(type: "text", nullable: true), + RetryCount = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionTasks", x => x.TaskId); + }); + + migrationBuilder.CreateTable( + name: "CNCDevices", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceCode = table.Column(type: "varchar(50)", nullable: false), + DeviceName = table.Column(type: "varchar(100)", nullable: false), + IPAddress = table.Column(type: "varchar(15)", nullable: false), + HttpUrl = table.Column(type: "varchar(255)", nullable: false), + CollectionInterval = table.Column(type: "int", nullable: false), + TemplateId = table.Column(type: "int", nullable: false), + IsAvailable = table.Column(type: "tinyint(1)", nullable: false), + IsOnline = table.Column(type: "tinyint(1)", nullable: false), + LastCollectionTime = table.Column(type: "datetime", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CNCDevices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CNCTemplates", + columns: table => new + { + TemplateId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + TemplateName = table.Column(type: "varchar(100)", nullable: false), + BrandName = table.Column(type: "varchar(50)", nullable: false), + Version = table.Column(type: "varchar(20)", nullable: false), + Description = table.Column(type: "text", nullable: true), + TagsJson = table.Column(type: "text", nullable: true), + DataProcessingRulesJson = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CNCTemplates", x => x.TemplateId); + }); + + migrationBuilder.CreateTable( + name: "DeviceAssignments", + columns: table => new + { + AssignmentId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + EmployeeId = table.Column(type: "int", nullable: false), + AssignmentDate = table.Column(type: "datetime", nullable: false), + AssignmentType = table.Column(type: "varchar(20)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceAssignments", x => x.AssignmentId); + }); + + migrationBuilder.CreateTable( + name: "DeviceStatus", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + Status = table.Column(type: "varchar(20)", nullable: false), + IsRunning = table.Column(type: "tinyint(1)", nullable: false), + NCProgram = table.Column(type: "varchar(100)", nullable: true), + CumulativeCount = table.Column(type: "int", nullable: false), + OperatingMode = table.Column(type: "varchar(20)", nullable: true), + RecordTime = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceStatus", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Employees", + columns: table => new + { + EmployeeId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + FirstName = table.Column(type: "varchar(50)", nullable: false), + LastName = table.Column(type: "varchar(50)", nullable: false), + Department = table.Column(type: "varchar(50)", nullable: false), + Position = table.Column(type: "varchar(50)", nullable: true), + HireDate = table.Column(type: "datetime", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Employees", x => x.EmployeeId); + }); + + migrationBuilder.CreateTable( + name: "LogEntries", + columns: table => new + { + LogId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + LogLevel = table.Column(type: "int", nullable: false), + Category = table.Column(type: "varchar(50)", nullable: false), + Message = table.Column(type: "text", nullable: false), + ExceptionMessage = table.Column(type: "text", nullable: true), + StackTrace = table.Column(type: "text", nullable: true), + Properties = table.Column(type: "json", nullable: true), + Timestamp = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LogEntries", x => x.LogId); + }); + + migrationBuilder.CreateTable( + name: "PasswordResets", + columns: table => new + { + ResetId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + UserId = table.Column(type: "int", nullable: false), + Token = table.Column(type: "varchar(255)", nullable: false), + ExpiresAt = table.Column(type: "datetime", nullable: false), + IsUsed = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PasswordResets", x => x.ResetId); + table.ForeignKey( + name: "FK_PasswordResets_Users", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Permissions", + columns: table => new + { + PermissionId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + PermissionName = table.Column(type: "varchar(100)", nullable: false), + Description = table.Column(type: "text", nullable: true), + Category = table.Column(type: "varchar(50)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => x.PermissionId); + }); + + migrationBuilder.CreateTable( + name: "ProductionRecords", + columns: table => new + { + RecordId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + DeviceName = table.Column(type: "varchar(100)", nullable: false), + ProgramName = table.Column(type: "varchar(100)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + ProductionDate = table.Column(type: "datetime", nullable: false), + ProductionTime = table.Column(type: "time", nullable: false), + IsCompleted = table.Column(type: "tinyint(1)", nullable: false), + Operator = table.Column(type: "varchar(50)", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionRecords", x => x.RecordId); + }); + + migrationBuilder.CreateTable( + name: "ProgramProductionSummaries", + columns: table => new + { + SummaryId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + DeviceId = table.Column(type: "int", nullable: false), + DeviceName = table.Column(type: "varchar(100)", nullable: false), + ProgramName = table.Column(type: "varchar(100)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + ProductionDate = table.Column(type: "date", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProgramProductionSummaries", x => x.SummaryId); + }); + + migrationBuilder.CreateTable( + name: "ProductionSummaries", + columns: table => new + { + SummaryId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + ProductionDate = table.Column(type: "date", nullable: false), + DeviceId = table.Column(type: "int", nullable: false), + DeviceName = table.Column(type: "varchar(100)", nullable: false), + TotalQuantity = table.Column(type: "int", nullable: false), + ProgramCount = table.Column(type: "int", nullable: false), + TotalProductionTime = table.Column(type: "time", nullable: true), + QualityRate = table.Column(type: "decimal(5,2)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionSummaries", x => x.SummaryId); + }); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + RoleId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + RoleName = table.Column(type: "varchar(50)", nullable: false), + Description = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.RoleId); + }); + + migrationBuilder.CreateTable( + name: "ScheduledTasks", + columns: table => new + { + TaskId = table.Column(type: "varchar(50)", nullable: false), + TaskName = table.Column(type: "varchar(100)", nullable: false), + CronExpression = table.Column(type: "varchar(100)", nullable: false), + Description = table.Column(type: "text", nullable: true), + TaskStatus = table.Column(type: "int", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + LastRunAt = table.Column(type: "datetime", nullable: true), + NextRunTime = table.Column(type: "datetime", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false), + CompletedAt = table.Column(type: "datetime", nullable: true), + ErrorMessage = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScheduledTasks", x => x.TaskId); + }); + + migrationBuilder.CreateTable( + name: "StatisticResults", + columns: table => new + { + ResultId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + RuleId = table.Column(type: "int", nullable: false), + ResultDate = table.Column(type: "datetime", nullable: false), + ResultValue = table.Column(type: "decimal(18,4)", nullable: false), + ResultData = table.Column(type: "json", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StatisticResults", x => x.ResultId); + table.ForeignKey( + name: "FK_StatisticResults_StatisticRules", + column: x => x.RuleId, + principalTable: "StatisticRules", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "StatisticRules", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + RuleName = table.Column(type: "varchar(100)", nullable: false), + Description = table.Column(type: "text", nullable: true), + MetricFormula = table.Column(type: "text", nullable: false), + GroupByDimensions = table.Column(type: "text", nullable: false), + IsEnabled = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StatisticRules", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SystemConfigs", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + ConfigKey = table.Column(type: "varchar(100)", nullable: false), + ConfigValue = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true), + Category = table.Column(type: "varchar(50)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + IsDefault = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemConfigs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TagMappings", + columns: table => new + { + MappingId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + TemplateId = table.Column(type: "int", nullable: false), + DeviceTagId = table.Column(type: "varchar(50)", nullable: false), + SystemTagId = table.Column(type: "varchar(50)", nullable: false), + DataType = table.Column(type: "varchar(20)", nullable: false), + ConversionFormula = table.Column(type: "varchar(100)", nullable: true), + Description = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TagMappings", x => x.MappingId); + }); + + migrationBuilder.CreateTable( + name: "TemplateFieldMappings", + columns: table => new + { + MappingId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + TemplateId = table.Column(type: "int", nullable: false), + FieldName = table.Column(type: "varchar(50)", nullable: false), + FieldValue = table.Column(type: "text", nullable: true), + DataType = table.Column(type: "varchar(20)", nullable: false), + IsRequired = table.Column(type: "tinyint(1)", nullable: false), + ValidationRegex = table.Column(type: "varchar(200)", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TemplateFieldMappings", x => x.MappingId); + }); + + migrationBuilder.CreateTable( + name: "TaskExecutionResults", + columns: table => new + { + ExecutionId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + TaskId = table.Column(type: "varchar(50)", nullable: false), + Status = table.Column(type: "int", nullable: false), + ExecutionTime = table.Column(type: "datetime", nullable: false), + ExecutionDurationMs = table.Column(type: "time", nullable: true), + ErrorMessage = table.Column(type: "text", nullable: true), + ResultData = table.Column(type: "json", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TaskExecutionResults", x => x.ExecutionId); + table.ForeignKey( + name: "FK_TaskExecutionResults_ScheduledTasks", + column: x => x.TaskId, + principalTable: "ScheduledTasks", + principalColumn: "TaskId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityIncrement), + Username = table.Column(type: "varchar(50)", nullable: false), + Email = table.Column(type: "varchar(100)", nullable: false), + PasswordHash = table.Column(type: "varchar(255)", nullable: false), + FirstName = table.Column(type: "varchar(50)", nullable: false), + LastName = table.Column(type: "varchar(50)", nullable: false), + Department = table.Column(type: "varchar(50)", nullable: true), + RoleId = table.Column(type: "int", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + LastLogin = table.Column(type: "datetime", nullable: true), + CreatedAt = table.Column(type: "datetime", nullable: false), + UpdatedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_Roles", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "RoleId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserPermissions", + columns: table => new + { + UserId = table.Column(type: "int", nullable: false), + PermissionId = table.Column(type: "int", nullable: false), + AssignedAt = table.Column(type: "datetime", nullable: false), + AssignedBy = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserPermissions", x => new { x.UserId, x.PermissionId }); + table.ForeignKey( + name: "FK_UserPermissions_Permissions", + column: x => x.PermissionId, + principalTable: "Permissions", + principalColumn: "PermissionId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserPermissions_Users", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RolePermissions", + columns: table => new + { + RoleId = table.Column(type: "int", nullable: false), + PermissionId = table.Column(type: "int", nullable: false), + AssignedAt = table.Column(type: "datetime", nullable: false), + AssignedBy = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RolePermissions", x => new { x.RoleId, x.PermissionId }); + table.ForeignKey( + name: "FK_RolePermissions_Permissions", + column: x => x.PermissionId, + principalTable: "Permissions", + principalColumn: "PermissionId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RolePermissions_Roles", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "RoleId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserSessions", + columns: table => new + { + SessionId = table.Column(type: "varchar(100)", nullable: false), + UserId = table.Column(type: "int", nullable: false), + RefreshToken = table.Column(type: "varchar(255)", nullable: false), + ExpiresAt = table.Column(type: "datetime", nullable: false), + CreatedAt = table.Column(type: "datetime", nullable: false), + LastAccessedAt = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserSessions", x => x.SessionId); + table.ForeignKey( + name: "FK_UserSessions_Users", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AlarmNotifications_AlarmId", + table: "AlarmNotifications", + column: "AlarmId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionConfigs_DeviceId", + table: "CollectionConfigs", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionLogs_DeviceId", + table: "CollectionLogs", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionResults_DeviceId", + table: "CollectionResults", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionTasks_DeviceId", + table: "CollectionTasks", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_CNCDevices_TemplateId", + table: "CNCDevices", + column: "TemplateId"); + + migrationBuilder.CreateIndex( + name: "IX_DeviceAssignments_DeviceId", + table: "DeviceAssignments", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_DeviceAssignments_EmployeeId", + table: "DeviceAssignments", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_PasswordResets_UserId", + table: "PasswordResets", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionRecords_DeviceId", + table: "ProductionRecords", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionRecords_ProductionDate", + table: "ProductionRecords", + column: "ProductionDate"); + + migrationBuilder.CreateIndex( + name: "IX_ProgramProductionSummaries_DeviceId", + table: "ProgramProductionSummaries", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_ProgramProductionSummaries_ProductionDate", + table: "ProgramProductionSummaries", + column: "ProductionDate"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionSummaries_DeviceId", + table: "ProductionSummaries", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionSummaries_ProductionDate", + table: "ProductionSummaries", + column: "ProductionDate"); + + migrationBuilder.CreateIndex( + name: "IX_StatisticResults_RuleId", + table: "StatisticResults", + column: "RuleId"); + + migrationBuilder.CreateIndex( + name: "IX_TagMappings_TemplateId", + table: "TagMappings", + column: "TemplateId"); + + migrationBuilder.CreateIndex( + name: "IX_TaskExecutionResults_TaskId", + table: "TaskExecutionResults", + column: "TaskId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_RoleId", + table: "Users", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_Username", + table: "Users", + column: "Username", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserPermissions_PermissionId", + table: "UserPermissions", + column: "PermissionId"); + + migrationBuilder.CreateIndex( + name: "IX_UserPermissions_UserId", + table: "UserPermissions", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_RolePermissions_PermissionId", + table: "RolePermissions", + column: "PermissionId"); + + migrationBuilder.CreateIndex( + name: "IX_RolePermissions_RoleId", + table: "RolePermissions", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_UserSessions_UserId", + table: "UserSessions", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AlarmNotifications"); + + migrationBuilder.DropTable( + name: "UserPermissions"); + + migrationBuilder.DropTable( + name: "RolePermissions"); + + migrationBuilder.DropTable( + name: "UserSessions"); + + migrationBuilder.DropTable( + name: "TaskExecutionResults"); + + migrationBuilder.DropTable( + name: "TagMappings"); + + migrationBuilder.DropTable( + name: "ProductionSummaries"); + + migrationBuilder.DropTable( + name: "ProgramProductionSummaries"); + + migrationBuilder.DropTable( + name: "ProductionRecords"); + + migrationBuilder.DropTable( + name: "StatisticResults"); + + migrationBuilder.DropTable( + name: "RolePermissions"); + + migrationBuilder.DropTable( + name: "UserPermissions"); + + migrationBuilder.DropTable( + name: "ScheduledTasks"); + + migrationBuilder.DropTable( + name: "PasswordResets"); + + migrationBuilder.DropTable( + name: "TemplateFieldMappings"); + + migrationBuilder.DropTable( + name: "UserSessions"); + + migrationBuilder.DropTable( + name: "StatisticRules"); + + migrationBuilder.DropTable( + name: "SystemConfigs"); + + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropTable( + name: "ProductionRecords"); + + migrationBuilder.DropTable( + name: "ProgramProductionSummaries"); + + migrationBuilder.DropTable( + name: "ProductionSummaries"); + + migrationBuilder.DropTable( + name: "Employees"); + + migrationBuilder.DropTable( + name: "LogEntries"); + + migrationBuilder.DropTable( + name: "UserPermissions"); + + migrationBuilder.DropTable( + name: "RolePermissions"); + + migrationBuilder.DropTable( + name: "UserSessions"); + + migrationBuilder.DropTable( + name: "PasswordResets"); + + migrationBuilder.DropTable( + name: "Permissions"); + + migrationBuilder.DropTable( + name: "CollectionResults"); + + migrationBuilder.DropTable( + name: "CollectionTasks"); + + migrationBuilder.DropTable( + name: "CollectionLogs"); + + migrationBuilder.DropTable( + name: "CollectionConfigs"); + + migrationBuilder.DropTable( + name: "TaskExecutionResults"); + + migrationBuilder.DropTable( + name: "TagMappings"); + + migrationBuilder.DropTable( + name: "TemplateFieldMappings"); + + migrationBuilder.DropTable( + name: "ScheduledTasks"); + + migrationBuilder.DropTable( + name: "SystemConfigs"); + + migrationBuilder.DropTable( + name: "LogEntries"); + + migrationBuilder.DropTable( + name: "PasswordResets"); + + migrationBuilder.DropTable( + name: "Employees"); + + migrationBuilder.DropTable( + name: "UserPermissions"); + + migrationBuilder.DropTable( + name: "RolePermissions"); + + migrationBuilder.DropTable( + name: "UserSessions"); + + migrationBuilder.DropTable( + name: "Permissions"); + + migrationBuilder.DropTable( + name: "CollectionResults"); + + migrationBuilder.DropTable( + name: "CollectionTasks"); + + migrationBuilder.DropTable( + name: "CollectionLogs"); + + migrationBuilder.DropTable( + name: "CollectionConfigs"); + + migrationBuilder.DropTable( + name: "DeviceAssignments"); + + migrationBuilder.DropTable( + name: "DeviceStatus"); + + migrationBuilder.DropTable( + name: "CNCTemplates"); + + migrationBuilder.DropTable( + name: "CNCDevices"); + + migrationBuilder.DropTable( + name: "StatisticResults"); + + migrationBuilder.DropTable( + name: "StatisticRules"); + + migrationBuilder.DropTable( + name: "ProductionSummaries"); + + migrationBuilder.DropTable( + name: "ProgramProductionSummaries"); + + migrationBuilder.DropTable( + name: "ProductionRecords"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropTable( + name: "TaskExecutionResults"); + + migrationBuilder.DropTable( + name: "TagMappings"); + + migrationBuilder.DropTable( + name: "TemplateFieldMappings"); + + migrationBuilder.DropTable( + name: "SystemConfigs"); + + migrationBuilder.DropTable( + name: "LogEntries"); + + migrationBuilder.DropTable( + name: "PasswordResets"); + + migrationBuilder.DropTable( + name: "Employees"); + + migrationBuilder.DropTable( + name: "UserPermissions"); + + migrationBuilder.DropTable( + name: "RolePermissions"); + + migrationBuilder.DropTable( + name: "UserSessions"); + + migrationBuilder.DropTable( + name: "Permissions"); + + migrationBuilder.DropTable( + name: "AlarmNotifications"); + + migrationBuilder.DropTable( + name: "AlarmRules"); + + migrationBuilder.DropTable( + name: "Alarms"); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/AlarmRepository.cs b/Haoliang.Data/Repositories/AlarmRepository.cs new file mode 100644 index 0000000..30ffbce --- /dev/null +++ b/Haoliang.Data/Repositories/AlarmRepository.cs @@ -0,0 +1,132 @@ +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 + { + Task> GetByDeviceIdAsync(int deviceId); + Task> GetByAlarmTypeAsync(AlarmType type); + Task> GetByStatusAsync(AlarmStatus status); + Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate); + Task GetAlarmStatisticsAsync(DateTime date); + Task> GetBySeverityAsync(AlarmSeverity severity); + Task> GetByDeviceAndDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate); + Task CountActiveAlarmsAsync(); + Task> GetActiveAlarmsAsync(); + Task> GetAlarmsByPriorityAsync(AlarmPriority priority); + } + + public class AlarmRepository : Repository, IAlarmRepository + { + private readonly CNCDbContext _context; + + public AlarmRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task> GetByDeviceIdAsync(int deviceId) + { + return await _context.Alarms + .Where(a => a.DeviceId == deviceId) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + } + + public async Task> GetByAlarmTypeAsync(AlarmType type) + { + return await _context.Alarms + .Where(a => a.AlarmType == type) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + } + + public async Task> GetByStatusAsync(AlarmStatus status) + { + return await _context.Alarms + .Where(a => a.Status == status) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + } + + public async Task> 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 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> GetBySeverityAsync(AlarmSeverity severity) + { + return await _context.Alarms + .Where(a => a.Severity == severity) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + } + + public async Task> 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 CountActiveAlarmsAsync() + { + return await _context.Alarms + .CountAsync(a => a.Status == AlarmStatus.Active); + } + + public async Task> GetActiveAlarmsAsync() + { + return await _context.Alarms + .Where(a => a.Status == AlarmStatus.Active) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + } + + public async Task> GetAlarmsByPriorityAsync(AlarmPriority priority) + { + return await _context.Alarms + .Where(a => a.Priority == priority) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/CollectionLogRepository.cs b/Haoliang.Data/Repositories/CollectionLogRepository.cs new file mode 100644 index 0000000..72e296e --- /dev/null +++ b/Haoliang.Data/Repositories/CollectionLogRepository.cs @@ -0,0 +1,126 @@ +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 + { + Task> GetByDeviceAsync(int deviceId); + Task> GetByLogLevelAsync(LogLevel logLevel); + Task GetErrorCountAsync(int deviceId); + Task ArchiveLogsAsync(int daysToKeep = 30); + Task ClearLogsAsync(); + Task> GetRecentLogsAsync(int count = 100); + Task GetLogStatisticsAsync(DateTime date); + Task> GetLogsByCategoryAsync(string category); + Task LogExistsAsync(int logId); + } + + public class CollectionLogRepository : Repository, ICollectionLogRepository + { + private readonly CNCDbContext _context; + + public CollectionLogRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task> GetByDeviceAsync(int deviceId) + { + return await _context.CollectionLogs + .Where(l => l.DeviceId == deviceId) + .OrderByDescending(l => l.LogTime) + .ToListAsync(); + } + + public async Task> GetByLogLevelAsync(LogLevel logLevel) + { + return await _context.CollectionLogs + .Where(l => l.LogLevel == logLevel) + .OrderByDescending(l => l.LogTime) + .ToListAsync(); + } + + public async Task 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> GetRecentLogsAsync(int count = 100) + { + return await _context.CollectionLogs + .OrderByDescending(l => l.LogTime) + .Take(count) + .ToListAsync(); + } + + public async Task 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> GetLogsByCategoryAsync(string category) + { + return await _context.CollectionLogs + .Where(l => l.LogCategory == category) + .OrderByDescending(l => l.LogTime) + .ToListAsync(); + } + + public async Task LogExistsAsync(int logId) + { + return await _context.CollectionLogs + .AnyAsync(l => l.LogId == logId); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/CollectionResultRepository.cs b/Haoliang.Data/Repositories/CollectionResultRepository.cs new file mode 100644 index 0000000..7e9fb84 --- /dev/null +++ b/Haoliang.Data/Repositories/CollectionResultRepository.cs @@ -0,0 +1,216 @@ +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 + { + Task> GetByDeviceAsync(int deviceId); + Task> GetByDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate); + Task GetStatisticsAsync(DateTime date); + Task GetHealthAsync(); + Task ArchiveResultsAsync(int daysToKeep = 30); + Task> GetSuccessfulResultsAsync(int deviceId, DateTime date); + Task> GetFailedResultsAsync(int deviceId, DateTime date); + Task GetAverageResponseTimeAsync(int deviceId, DateTime date); + Task GetSuccessRateAsync(int deviceId, DateTime date); + } + + public class CollectionResultRepository : Repository, ICollectionResultRepository + { + private readonly CNCDbContext _context; + + public CollectionResultRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task> GetByDeviceAsync(int deviceId) + { + return await _context.CollectionResults + .Where(r => r.DeviceId == deviceId) + .OrderByDescending(r => r.CollectionTime) + .ToListAsync(); + } + + public async Task> 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 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 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> 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> 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 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 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; + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/CollectionTaskRepository.cs b/Haoliang.Data/Repositories/CollectionTaskRepository.cs new file mode 100644 index 0000000..bae709f --- /dev/null +++ b/Haoliang.Data/Repositories/CollectionTaskRepository.cs @@ -0,0 +1,132 @@ +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 + { + Task> GetPendingTasksAsync(); + Task> GetFailedTasksAsync(); + Task GetByDeviceAsync(int deviceId); + Task MarkTaskCompletedAsync(int taskId, bool isSuccess, string result); + Task> GetTasksByDateAsync(DateTime date); + Task> GetRunningTasksAsync(); + Task GetTaskStatisticsAsync(DateTime date); + Task HasPendingTasksAsync(int deviceId); + Task> GetTasksByStatusAsync(string status); + } + + public class CollectionTaskRepository : Repository, ICollectionTaskRepository + { + private readonly CNCDbContext _context; + + public CollectionTaskRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task> GetPendingTasksAsync() + { + return await _context.CollectionTasks + .Where(t => t.Status == "Pending") + .OrderBy(t => t.ScheduledTime) + .ToListAsync(); + } + + public async Task> GetFailedTasksAsync() + { + return await _context.CollectionTasks + .Where(t => t.Status == "Failed") + .OrderByDescending(t => t.CreatedAt) + .ToListAsync(); + } + + public async Task GetByDeviceAsync(int deviceId) + { + return await _context.CollectionTasks + .Where(t => t.DeviceId == deviceId) + .OrderByDescending(t => t.CreatedAt) + .FirstOrDefaultAsync(); + } + + public async Task 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> 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> GetRunningTasksAsync() + { + return await _context.CollectionTasks + .Where(t => t.Status == "Running") + .OrderBy(t => t.ScheduledTime) + .ToListAsync(); + } + + public async Task 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 HasPendingTasksAsync(int deviceId) + { + return await _context.CollectionTasks + .AnyAsync(t => t.DeviceId == deviceId && t.Status == "Pending"); + } + + public async Task> GetTasksByStatusAsync(string status) + { + return await _context.CollectionTasks + .Where(t => t.Status == status) + .OrderBy(t => t.ScheduledTime) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/LogRepository.cs b/Haoliang.Data/Repositories/LogRepository.cs new file mode 100644 index 0000000..d11c088 --- /dev/null +++ b/Haoliang.Data/Repositories/LogRepository.cs @@ -0,0 +1,171 @@ +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 ILogRepository : IRepository + { + Task> GetLogsAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null, string category = null); + Task GetLogCountAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null); + Task ArchiveLogsAsync(DateTime cutoffDate); + Task ClearLogsAsync(); + Task> GetRecentLogsAsync(int count = 100); + Task> GetErrorLogsAsync(DateTime? startDate = null, DateTime? endDate = null); + Task GetLogStatisticsAsync(DateTime date); + Task LogExistsAsync(string logId); + Task> GetLogsBySourceAsync(string source); + } + + public class LogRepository : Repository, ILogRepository + { + private readonly CNCDbContext _context; + + public LogRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task> GetLogsAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null, string category = null) + { + var query = _context.Logs.AsQueryable(); + + if (logLevel.HasValue) + { + query = query.Where(l => l.LogLevel == logLevel.Value); + } + + if (startDate.HasValue) + { + query = query.Where(l => l.LogTime >= startDate.Value); + } + + if (endDate.HasValue) + { + query = query.Where(l => l.LogTime <= endDate.Value); + } + + if (!string.IsNullOrEmpty(category)) + { + query = query.Where(l => l.Category == category); + } + + return await query + .OrderByDescending(l => l.LogTime) + .ToListAsync(); + } + + public async Task GetLogCountAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null) + { + var query = _context.Logs.AsQueryable(); + + if (logLevel.HasValue) + { + query = query.Where(l => l.LogLevel == logLevel.Value); + } + + if (startDate.HasValue) + { + query = query.Where(l => l.LogTime >= startDate.Value); + } + + if (endDate.HasValue) + { + query = query.Where(l => l.LogTime <= endDate.Value); + } + + return await query.CountAsync(); + } + + public async Task ArchiveLogsAsync(DateTime cutoffDate) + { + var logsToArchive = await _context.Logs + .Where(l => l.LogTime < cutoffDate) + .ToListAsync(); + + if (logsToArchive.Any()) + { + // In a real implementation, you would move these to an archive table or file + // For now, we'll just delete them + _context.Logs.RemoveRange(logsToArchive); + await SaveAsync(); + } + } + + public async Task ClearLogsAsync() + { + _context.Logs.RemoveRange(_context.Logs); + await SaveAsync(); + } + + public async Task> GetRecentLogsAsync(int count = 100) + { + return await _context.Logs + .OrderByDescending(l => l.LogTime) + .Take(count) + .ToListAsync(); + } + + public async Task> GetErrorLogsAsync(DateTime? startDate = null, DateTime? endDate = null) + { + var query = _context.Logs + .Where(l => l.LogLevel == LogLevel.Error || l.LogLevel == LogLevel.Critical); + + if (startDate.HasValue) + { + query = query.Where(l => l.LogTime >= startDate.Value); + } + + if (endDate.HasValue) + { + query = query.Where(l => l.LogTime <= endDate.Value); + } + + return await query + .OrderByDescending(l => l.LogTime) + .ToListAsync(); + } + + public async Task GetLogStatisticsAsync(DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + var logs = await _context.Logs + .Where(l => l.LogTime >= startOfDay && l.LogTime <= endOfDay) + .ToListAsync(); + + var stats = new LogStatistics + { + 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), + LogSources = logs.GroupBy(l => l.Source) + .ToDictionary(g => g.Key, g => g.Count()) + }; + + return stats; + } + + public async Task LogExistsAsync(string logId) + { + return await _context.Logs + .AnyAsync(l => l.LogId == logId); + } + + public async Task> GetLogsBySourceAsync(string source) + { + return await _context.Logs + .Where(l => l.Source == source) + .OrderByDescending(l => l.LogTime) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/ProductionSummaryRepository.cs b/Haoliang.Data/Repositories/ProductionSummaryRepository.cs new file mode 100644 index 0000000..786a77e --- /dev/null +++ b/Haoliang.Data/Repositories/ProductionSummaryRepository.cs @@ -0,0 +1,207 @@ +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 IProductionSummaryRepository : IRepository + { + Task GetByDateAsync(DateTime date); + Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate); + Task GetByDeviceAndDateAsync(int deviceId, DateTime date); + Task GetTodaySummaryAsync(); + Task GetYesterdaySummaryAsync(); + Task GetWeeklySummaryAsync(DateTime weekStart); + Task GetMonthlySummaryAsync(int year, int month); + Task GetBestPerformingDeviceAsync(DateTime date); + Task GetWorstPerformingDeviceAsync(DateTime date); + Task ArchiveProductionSummariesAsync(int daysToKeep = 90); + } + + public class ProductionSummaryRepository : Repository, IProductionSummaryRepository + { + private readonly CNCDbContext _context; + + public ProductionSummaryRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task GetByDateAsync(DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + return await _context.ProductionSummaries + .FirstOrDefaultAsync(s => s.ProductionDate >= startOfDay && + s.ProductionDate < endOfDay); + } + + public async Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate) + { + return await _context.ProductionSummaries + .Where(s => s.ProductionDate >= startDate && + s.ProductionDate <= endDate) + .OrderBy(s => s.ProductionDate) + .ThenBy(s => s.DeviceName) + .ToListAsync(); + } + + public async Task GetByDeviceAndDateAsync(int deviceId, DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + return await _context.ProductionSummaries + .FirstOrDefaultAsync(s => s.DeviceId == deviceId && + s.ProductionDate >= startOfDay && + s.ProductionDate < endOfDay); + } + + public async Task GetTodaySummaryAsync() + { + return await GetByDateAsync(DateTime.Today); + } + + public async Task GetYesterdaySummaryAsync() + { + return await GetByDateAsync(DateTime.Today.AddDays(-1)); + } + + public async Task GetWeeklySummaryAsync(DateTime weekStart) + { + var weekEnd = weekStart.AddDays(7); + + var summaries = await _context.ProductionSummaries + .Where(s => s.ProductionDate >= weekStart && + s.ProductionDate < weekEnd) + .ToListAsync(); + + var weeklySummary = new WeeklyProductionSummary + { + WeekStart = weekStart, + WeekEnd = weekEnd, + TotalDevices = summaries.Select(s => s.DeviceId).Distinct().Count(), + TotalQuantity = summaries.Sum(s => s.TotalQuantity), + AverageDailyQuantity = summaries.Any() ? summaries.Average(s => s.TotalQuantity) : 0, + DailySummaries = summaries + .GroupBy(s => s.ProductionDate) + .Select(g => new DailyProductionSummary + { + Date = g.Key, + TotalQuantity = g.Sum(s => s.TotalQuantity), + DeviceCount = g.Select(s => s.DeviceId).Distinct().Count() + }) + .OrderBy(d => d.Date) + .ToList() + }; + + return weeklySummary; + } + + public async Task GetMonthlySummaryAsync(int year, int month) + { + var monthStart = new DateTime(year, month, 1); + var monthEnd = monthStart.AddMonths(1); + + var summaries = await _context.ProductionSummaries + .Where(s => s.ProductionDate >= monthStart && + s.ProductionDate < monthEnd) + .ToListAsync(); + + var monthlySummary = new MonthlyProductionSummary + { + Year = year, + Month = month, + TotalDevices = summaries.Select(s => s.DeviceId).Distinct().Count(), + TotalQuantity = summaries.Sum(s => s.TotalQuantity), + AverageDailyQuantity = summaries.Any() ? summaries.Average(s => s.TotalQuantity) : 0, + WeeklySummaries = new List() + }; + + // Group by week + var weeks = summaries + .GroupBy(s => s.ProductionDate - TimeSpan.FromDays((int)s.ProductionDate.DayOfWeek)) + .ToList(); + + foreach (var week in weeks) + { + var weeklySummary = new WeeklyProductionSummary + { + WeekStart = week.Key, + WeekEnd = week.Key.AddDays(7), + TotalQuantity = week.Sum(s => s.TotalQuantity), + DailySummaries = week + .GroupBy(s => s.ProductionDate) + .Select(g => new DailyProductionSummary + { + Date = g.Key, + TotalQuantity = g.Sum(s => s.TotalQuantity), + DeviceCount = g.Select(s => s.DeviceId).Distinct().Count() + }) + .OrderBy(d => d.Date) + .ToList() + }; + monthlySummary.WeeklySummaries.Add(weeklySummary); + } + + return monthlySummary; + } + + public async Task GetBestPerformingDeviceAsync(DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + var summaries = await _context.ProductionSummaries + .Where(s => s.ProductionDate >= startOfDay && + s.ProductionDate < endOfDay) + .ToListAsync(); + + if (!summaries.Any()) + return null; + + return summaries + .OrderByDescending(s => s.TotalQuantity) + .FirstOrDefault(); + } + + public async Task GetWorstPerformingDeviceAsync(DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + var summaries = await _context.ProductionSummaries + .Where(s => s.ProductionDate >= startOfDay && + s.ProductionDate < endOfDay) + .ToListAsync(); + + if (!summaries.Any()) + return null; + + return summaries + .OrderBy(s => s.TotalQuantity) + .FirstOrDefault(); + } + + public async Task ArchiveProductionSummariesAsync(int daysToKeep = 90) + { + var cutoffDate = DateTime.Today.AddDays(-daysToKeep); + + var summariesToArchive = await _context.ProductionSummaries + .Where(s => s.ProductionDate < cutoffDate) + .ToListAsync(); + + if (summariesToArchive.Any()) + { + // In a real implementation, you would move these to an archive table + _context.ProductionSummaries.RemoveRange(summariesToArchive); + await SaveAsync(); + } + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/ProgramProductionSummaryRepository.cs b/Haoliang.Data/Repositories/ProgramProductionSummaryRepository.cs new file mode 100644 index 0000000..6d0bec5 --- /dev/null +++ b/Haoliang.Data/Repositories/ProgramProductionSummaryRepository.cs @@ -0,0 +1,176 @@ +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 + { + Task GetByDeviceAndDateAsync(int deviceId, DateTime date); + Task> GetByDateAsync(DateTime date); + Task> GetByDeviceAsync(int deviceId); + Task> GetByProgramAsync(string programName); + Task GetByDeviceAndProgramAsync(int deviceId, string programName); + Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate); + Task GetTotalProductionAsync(DateTime date); + Task UpdateProductionSummaryAsync(int deviceId, string programName, int quantity); + Task ArchiveProductionSummariesAsync(int daysToKeep = 90); + } + + public class ProgramProductionSummaryRepository : Repository, IProgramProductionSummaryRepository + { + private readonly CNCDbContext _context; + + public ProgramProductionSummaryRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task 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> 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> GetByDeviceAsync(int deviceId) + { + return await _context.ProgramProductionSummaries + .Where(p => p.DeviceId == deviceId) + .OrderByDescending(p => p.ProductionDate) + .ThenBy(p => p.ProgramName) + .ToListAsync(); + } + + public async Task> GetByProgramAsync(string programName) + { + return await _context.ProgramProductionSummaries + .Where(p => p.ProgramName == programName) + .OrderByDescending(p => p.ProductionDate) + .ThenBy(p => p.DeviceName) + .ToListAsync(); + } + + public async Task 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> 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 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 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(); + } + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/ScheduledTaskRepository.cs b/Haoliang.Data/Repositories/ScheduledTaskRepository.cs new file mode 100644 index 0000000..25f977f --- /dev/null +++ b/Haoliang.Data/Repositories/ScheduledTaskRepository.cs @@ -0,0 +1,154 @@ +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 IScheduledTaskRepository : IRepository + { + Task> GetActiveTasksAsync(); + Task> GetTasksByStatusAsync(TaskStatus status); + Task GetLastExecutionResultAsync(string taskId); + Task> GetTasksByCronExpressionAsync(string cronExpression); + Task> GetOverdueTasksAsync(); + Task ExecuteTaskAsync(string taskId); + Task GetNextScheduledTaskAsync(); + Task GetExecutionSummaryAsync(DateTime date); + Task> GetExecutionHistoryAsync(string taskId, int count = 10); + } + + public class ScheduledTaskRepository : Repository, IScheduledTaskRepository + { + private readonly CNCDbContext _context; + + public ScheduledTaskRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task> GetActiveTasksAsync() + { + return await _context.ScheduledTasks + .Where(t => t.IsActive && t.TaskStatus != TaskStatus.Disabled) + .OrderBy(t => t.NextRunTime) + .ToListAsync(); + } + + public async Task> GetTasksByStatusAsync(TaskStatus status) + { + return await _context.ScheduledTasks + .Where(t => t.TaskStatus == status) + .OrderBy(t => t.CreatedAt) + .ToListAsync(); + } + + public async Task GetLastExecutionResultAsync(string taskId) + { + return await _context.TaskExecutionResults + .Where(r => r.TaskId == taskId) + .OrderByDescending(r => r.ExecutionTime) + .FirstOrDefaultAsync(); + } + + public async Task> GetTasksByCronExpressionAsync(string cronExpression) + { + return await _context.ScheduledTasks + .Where(t => t.CronExpression == cronExpression && t.IsActive) + .OrderBy(t => t.TaskName) + .ToListAsync(); + } + + public async Task> GetOverdueTasksAsync() + { + var now = DateTime.Now; + return await _context.ScheduledTasks + .Where(t => t.IsActive && + t.NextRunTime <= now && + t.TaskStatus != TaskStatus.Running) + .OrderBy(t => t.NextRunTime) + .ToListAsync(); + } + + public async Task ExecuteTaskAsync(string taskId) + { + var task = await GetByIdAsync(taskId); + if (task == null || !task.IsActive) + { + return false; + } + + task.TaskStatus = TaskStatus.Running; + task.LastRunAt = DateTime.Now; + await SaveAsync(); + + // Create execution result + var result = new TaskExecutionResult + { + TaskId = taskId, + ExecutionTime = DateTime.Now, + Status = TaskStatus.Running, + ErrorMessage = null + }; + + _context.TaskExecutionResults.Add(result); + await SaveAsync(); + + return true; + } + + public async Task GetNextScheduledTaskAsync() + { + var now = DateTime.Now; + return await _context.ScheduledTasks + .Where(t => t.IsActive && + t.NextRunTime <= now && + t.TaskStatus != TaskStatus.Running) + .OrderBy(t => t.NextRunTime) + .FirstOrDefaultAsync(); + } + + public async Task GetExecutionSummaryAsync(DateTime date) + { + var startOfDay = date.Date; + var endOfDay = startOfDay.AddDays(1); + + var executionResults = await _context.TaskExecutionResults + .Where(r => r.ExecutionTime >= startOfDay && r.ExecutionTime <= endOfDay) + .ToListAsync(); + + var summary = new TaskExecutionSummary + { + Date = date, + TotalExecutions = executionResults.Count, + SuccessfulExecutions = executionResults.Count(r => r.Status == TaskStatus.Completed), + FailedExecutions = executionResults.Count(r => r.Status == TaskStatus.Failed), + RunningExecutions = executionResults.Count(r => r.Status == TaskStatus.Running), + ExecutionDetails = executionResults + .GroupBy(r => r.TaskId) + .ToDictionary(g => g.Key, g => new TaskExecutionDetail + { + TaskName = g.FirstOrDefault()?.ScheduledTask?.TaskName ?? "", + TotalExecutions = g.Count(), + SuccessfulExecutions = g.Count(r => r.Status == TaskStatus.Completed), + FailedExecutions = g.Count(r => r.Status == TaskStatus.Failed), + AverageExecutionTime = g.Average(r => r.ExecutionDurationMs) + }) + }; + + return summary; + } + + public async Task> GetExecutionHistoryAsync(string taskId, int count = 10) + { + return await _context.TaskExecutionResults + .Where(r => r.TaskId == taskId) + .OrderByDescending(r => r.ExecutionTime) + .Take(count) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/Repositories/SystemConfigRepository.cs b/Haoliang.Data/Repositories/SystemConfigRepository.cs new file mode 100644 index 0000000..c3b40eb --- /dev/null +++ b/Haoliang.Data/Repositories/SystemConfigRepository.cs @@ -0,0 +1,140 @@ +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 + { + Task GetByKeyAsync(string configKey); + Task DeleteByKeyAsync(string configKey); + Task KeyExistsAsync(string configKey); + Task> GetByCategoryAsync(string category); + SystemConfig UpsertAsync(SystemConfig config); + Task GetValueAsync(string configKey); + Task SetValueAsync(string configKey, string value); + Task> GetActiveConfigsAsync(); + Task GetDefaultConfigAsync(); + Task UpdateConfigValueAsync(string configKey, string value); + } + + public class SystemConfigRepository : Repository, ISystemConfigRepository + { + private readonly CNCDbContext _context; + + public SystemConfigRepository(CNCDbContext context) : base(context) + { + _context = context; + } + + public async Task GetByKeyAsync(string configKey) + { + return await _context.SystemConfigs + .FirstOrDefaultAsync(c => c.ConfigKey == configKey); + } + + public async Task DeleteByKeyAsync(string configKey) + { + var config = await GetByKeyAsync(configKey); + if (config != null) + { + _context.SystemConfigs.Remove(config); + await SaveAsync(); + return true; + } + return false; + } + + public async Task KeyExistsAsync(string configKey) + { + return await _context.SystemConfigs + .AnyAsync(c => c.ConfigKey == configKey); + } + + public async Task> 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 GetValueAsync(string configKey) + { + var config = await GetByKeyAsync(configKey); + return config?.ConfigValue; + } + + public async Task 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> GetActiveConfigsAsync() + { + return await _context.SystemConfigs + .Where(c => c.IsActive) + .OrderBy(c => c.Category) + .ThenBy(c => c.ConfigKey) + .ToListAsync(); + } + + public async Task GetDefaultConfigAsync() + { + return await _context.SystemConfigs + .FirstOrDefaultAsync(c => c.IsDefault); + } + + public async Task 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; + } + } +} \ No newline at end of file diff --git a/Haoliang.Data/obj/Debug/net6.0/Haoliang.Data.assets.cache b/Haoliang.Data/obj/Debug/net6.0/Haoliang.Data.assets.cache index 1236a20..742e1d9 100644 Binary files a/Haoliang.Data/obj/Debug/net6.0/Haoliang.Data.assets.cache and b/Haoliang.Data/obj/Debug/net6.0/Haoliang.Data.assets.cache differ diff --git a/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.dgspec.json b/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.dgspec.json index 04d0f2a..058b447 100644 --- a/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.dgspec.json +++ b/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.dgspec.json @@ -41,6 +41,36 @@ "frameworks": { "net6.0": { "targetAlias": "net6.0", + "dependencies": { + "BCrypt.Net-Next": { + "target": "Package", + "version": "[4.0.3, )" + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "target": "Package", + "version": "[2.2.0, )" + }, + "Microsoft.AspNetCore.SignalR": { + "target": "Package", + "version": "[1.1.0, )" + }, + "Microsoft.Extensions.Caching.Memory": { + "target": "Package", + "version": "[6.0.0, )" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "target": "Package", + "version": "[6.0.0, )" + }, + "Microsoft.IdentityModel.Tokens": { + "target": "Package", + "version": "[6.26.0, )" + }, + "System.IdentityModel.Tokens.Jwt": { + "target": "Package", + "version": "[6.26.0, )" + } + }, "imports": [ "net461", "net462", @@ -101,9 +131,21 @@ "net6.0": { "targetAlias": "net6.0", "dependencies": { + "Microsoft.EntityFrameworkCore": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Design": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Tools": { + "target": "Package", + "version": "[7.0.2, )" + }, "Pomelo.EntityFrameworkCore.MySql": { "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.0, )" } }, "imports": [ diff --git a/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.props b/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.props index 294862a..372c971 100644 --- a/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.props +++ b/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.props @@ -14,5 +14,9 @@ + + + /root/.nuget/packages/microsoft.entityframeworkcore.tools/7.0.2 + \ No newline at end of file diff --git a/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.targets b/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.targets index b6ee7d6..eab753f 100644 --- a/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.targets +++ b/Haoliang.Data/obj/Haoliang.Data.csproj.nuget.g.targets @@ -1,6 +1,7 @@  + \ No newline at end of file diff --git a/Haoliang.Data/obj/project.assets.json b/Haoliang.Data/obj/project.assets.json index 6a36773..fe01c96 100644 --- a/Haoliang.Data/obj/project.assets.json +++ b/Haoliang.Data/obj/project.assets.json @@ -2,6 +2,317 @@ "version": 3, "targets": { "net6.0": { + "BCrypt.Net-Next/4.0.3": { + "type": "package", + "compile": { + "lib/net6.0/BCrypt.Net-Next.dll": {} + }, + "runtime": { + "lib/net6.0/BCrypt.Net-Next.dll": {} + } + }, + "Humanizer.Core/2.14.1": { + "type": "package", + "compile": { + "lib/net6.0/Humanizer.dll": {} + }, + "runtime": { + "lib/net6.0/Humanizer.dll": {} + } + }, + "Microsoft.AspNetCore.Authentication.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Authorization/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll": {} + } + }, + "Microsoft.AspNetCore.Authorization.Policy/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Authorization": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll": {} + } + }, + "Microsoft.AspNetCore.Connections.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.IO.Pipelines": "4.5.2" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Hosting.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Hosting.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Http/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.AspNetCore.WebUtilities": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Connections/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Authorization.Policy": "2.2.0", + "Microsoft.AspNetCore.Hosting.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http": "2.2.0", + "Microsoft.AspNetCore.Http.Connections.Common": "1.1.0", + "Microsoft.AspNetCore.Routing": "2.2.0", + "Microsoft.AspNetCore.WebSockets": "2.2.0", + "Newtonsoft.Json": "11.0.2", + "System.Security.Principal.Windows": "4.5.0" + }, + "compile": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.dll": {} + }, + "runtime": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Connections.Common/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Connections.Abstractions": "2.2.0", + "Newtonsoft.Json": "11.0.2", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Extensions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll": {} + } + }, + "Microsoft.AspNetCore.Http.Features/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {} + } + }, + "Microsoft.AspNetCore.Routing/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + }, + "compile": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.dll": {} + }, + "runtime": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.dll": {} + } + }, + "Microsoft.AspNetCore.Routing.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Connections": "1.1.0", + "Microsoft.AspNetCore.SignalR.Core": "1.1.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR.Common/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Connections.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Newtonsoft.Json": "11.0.2", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.dll": {} + }, + "runtime": { + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR.Core/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Authorization": "2.2.0", + "Microsoft.AspNetCore.SignalR.Common": "1.1.0", + "Microsoft.AspNetCore.SignalR.Protocols.Json": "1.1.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "System.Reflection.Emit": "4.3.0", + "System.Threading.Channels": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.dll": {} + } + }, + "Microsoft.AspNetCore.SignalR.Protocols.Json/1.1.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.SignalR.Common": "1.1.0", + "Newtonsoft.Json": "11.0.2" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.dll": {} + } + }, + "Microsoft.AspNetCore.WebSockets/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "System.Net.WebSockets.WebSocketProtocol": "4.5.1" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.dll": {} + } + }, + "Microsoft.AspNetCore.WebUtilities/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": {} + } + }, + "Microsoft.CSharp/4.5.0": { + "type": "package", + "compile": { + "ref/netcoreapp2.0/_._": {} + }, + "runtime": { + "lib/netcoreapp2.0/_._": {} + } + }, "Microsoft.EntityFrameworkCore/7.0.2": { "type": "package", "dependencies": { @@ -39,6 +350,24 @@ "lib/netstandard2.0/_._": {} } }, + "Microsoft.EntityFrameworkCore.Design/7.0.2": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.EntityFrameworkCore.Relational": "7.0.2", + "Microsoft.Extensions.DependencyModel": "7.0.0", + "Mono.TextTemplating": "2.2.1" + }, + "compile": { + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll": {} + }, + "build": { + "build/net6.0/Microsoft.EntityFrameworkCore.Design.props": {} + } + }, "Microsoft.EntityFrameworkCore.Relational/7.0.2": { "type": "package", "dependencies": { @@ -52,6 +381,18 @@ "lib/net6.0/Microsoft.EntityFrameworkCore.Relational.dll": {} } }, + "Microsoft.EntityFrameworkCore.Tools/7.0.2": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": "7.0.2" + }, + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/_._": {} + } + }, "Microsoft.Extensions.Caching.Abstractions/7.0.0": { "type": "package", "dependencies": { @@ -128,6 +469,49 @@ "buildTransitive/net6.0/_._": {} } }, + "Microsoft.Extensions.DependencyModel/7.0.0": { + "type": "package", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0", + "System.Text.Json": "7.0.0" + }, + "compile": { + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "Microsoft.Extensions.FileProviders.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {} + } + }, + "Microsoft.Extensions.Hosting.Abstractions/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll": {} + } + }, "Microsoft.Extensions.Logging/7.0.0": { "type": "package", "dependencies": { @@ -158,6 +542,15 @@ "buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets": {} } }, + "Microsoft.Extensions.ObjectPool/2.2.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": {} + } + }, "Microsoft.Extensions.Options/7.0.0": { "type": "package", "dependencies": { @@ -189,57 +582,397 @@ "buildTransitive/net6.0/_._": {} } }, - "MySqlConnector/2.2.5": { + "Microsoft.IdentityModel.Abstractions/6.26.0": { "type": "package", "compile": { - "lib/net6.0/MySqlConnector.dll": {} + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll": {} }, "runtime": { - "lib/net6.0/MySqlConnector.dll": {} + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll": {} } }, - "Pomelo.EntityFrameworkCore.MySql/7.0.0": { + "Microsoft.IdentityModel.JsonWebTokens/6.26.0": { "type": "package", "dependencies": { - "Microsoft.EntityFrameworkCore.Relational": "7.0.2", - "MySqlConnector": "2.2.5" + "Microsoft.IdentityModel.Tokens": "6.26.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.7.2" }, "compile": { - "lib/net6.0/Pomelo.EntityFrameworkCore.MySql.dll": {} + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll": {} }, "runtime": { - "lib/net6.0/Pomelo.EntityFrameworkCore.MySql.dll": {} + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll": {} } }, - "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "Microsoft.IdentityModel.Logging/6.26.0": { "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.26.0" + }, "compile": { - "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {} + "lib/net6.0/Microsoft.IdentityModel.Logging.dll": {} }, "runtime": { - "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {} - }, - "build": { - "buildTransitive/netcoreapp3.1/_._": {} + "lib/net6.0/Microsoft.IdentityModel.Logging.dll": {} } }, - "Haoliang.Core/1.0.0": { - "type": "project", - "framework": ".NETCoreApp,Version=v6.0", + "Microsoft.IdentityModel.Tokens/6.26.0": { + "type": "package", "dependencies": { - "Haoliang.Models": "1.0.0" + "Microsoft.CSharp": "4.5.0", + "Microsoft.IdentityModel.Logging": "6.26.0", + "System.Security.Cryptography.Cng": "4.5.0" }, "compile": { - "bin/placeholder/Haoliang.Core.dll": {} + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll": {} }, "runtime": { - "bin/placeholder/Haoliang.Core.dll": {} + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll": {} } }, - "Haoliang.Models/1.0.0": { - "type": "project", - "framework": ".NETCoreApp,Version=v6.0", - "compile": { + "Microsoft.Net.Http.Headers/2.2.0": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0", + "System.Buffers": "4.5.0" + }, + "compile": { + "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": {} + } + }, + "Microsoft.NETCore.Platforms/2.0.0": { + "type": "package", + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "Microsoft.NETCore.Targets/1.1.0": { + "type": "package", + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "Mono.TextTemplating/2.2.1": { + "type": "package", + "dependencies": { + "System.CodeDom": "4.4.0" + }, + "compile": { + "lib/netstandard2.0/Mono.TextTemplating.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Mono.TextTemplating.dll": {} + } + }, + "MySqlConnector/2.2.5": { + "type": "package", + "compile": { + "lib/net6.0/MySqlConnector.dll": {} + }, + "runtime": { + "lib/net6.0/MySqlConnector.dll": {} + } + }, + "Newtonsoft.Json/11.0.2": { + "type": "package", + "compile": { + "lib/netstandard2.0/Newtonsoft.Json.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": {} + } + }, + "Pomelo.EntityFrameworkCore.MySql/7.0.0": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Relational": "7.0.2", + "MySqlConnector": "2.2.5" + }, + "compile": { + "lib/net6.0/Pomelo.EntityFrameworkCore.MySql.dll": {} + }, + "runtime": { + "lib/net6.0/Pomelo.EntityFrameworkCore.MySql.dll": {} + } + }, + "System.Buffers/4.5.0": { + "type": "package", + "compile": { + "ref/netcoreapp2.0/_._": {} + }, + "runtime": { + "lib/netcoreapp2.0/_._": {} + } + }, + "System.CodeDom/4.4.0": { + "type": "package", + "compile": { + "ref/netstandard2.0/System.CodeDom.dll": {} + }, + "runtime": { + "lib/netstandard2.0/System.CodeDom.dll": {} + } + }, + "System.IdentityModel.Tokens.Jwt/6.26.0": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "6.26.0", + "Microsoft.IdentityModel.Tokens": "6.26.0" + }, + "compile": { + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll": {} + }, + "runtime": { + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll": {} + } + }, + "System.IO/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + }, + "compile": { + "ref/netstandard1.5/System.IO.dll": {} + } + }, + "System.IO.Pipelines/4.5.2": { + "type": "package", + "compile": { + "ref/netstandard1.3/System.IO.Pipelines.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.IO.Pipelines.dll": {} + } + }, + "System.Net.WebSockets.WebSocketProtocol/4.5.1": { + "type": "package", + "compile": { + "ref/netstandard2.0/System.Net.WebSockets.WebSocketProtocol.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.Net.WebSockets.WebSocketProtocol.dll": {} + } + }, + "System.Reflection/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.5/System.Reflection.dll": {} + } + }, + "System.Reflection.Emit/4.3.0": { + "type": "package", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.1/System.Reflection.Emit.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.dll": {} + } + }, + "System.Reflection.Emit.ILGeneration/4.3.0": { + "type": "package", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} + } + }, + "System.Reflection.Primitives/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.0/System.Reflection.Primitives.dll": {} + } + }, + "System.Runtime/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + }, + "compile": { + "ref/netstandard1.5/System.Runtime.dll": {} + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {} + }, + "runtime": { + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": {} + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "System.Security.Cryptography.Cng/4.5.0": { + "type": "package", + "compile": { + "ref/netcoreapp2.1/System.Security.Cryptography.Cng.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll": {} + }, + "runtimeTargets": { + "runtimes/win/lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Security.Principal.Windows/4.5.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.0.0" + }, + "compile": { + "ref/netstandard2.0/System.Security.Principal.Windows.dll": {} + }, + "runtime": { + "lib/netstandard2.0/System.Security.Principal.Windows.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netcoreapp2.0/System.Security.Principal.Windows.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netcoreapp2.0/System.Security.Principal.Windows.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Text.Encoding/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.3/System.Text.Encoding.dll": {} + } + }, + "System.Text.Encodings.Web/7.0.0": { + "type": "package", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "compile": { + "lib/net6.0/System.Text.Encodings.Web.dll": {} + }, + "runtime": { + "lib/net6.0/System.Text.Encodings.Web.dll": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + }, + "runtimeTargets": { + "runtimes/browser/lib/net6.0/System.Text.Encodings.Web.dll": { + "assetType": "runtime", + "rid": "browser" + } + } + }, + "System.Text.Json/7.0.0": { + "type": "package", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "7.0.0" + }, + "compile": { + "lib/net6.0/System.Text.Json.dll": {} + }, + "runtime": { + "lib/net6.0/System.Text.Json.dll": {} + }, + "build": { + "buildTransitive/net6.0/System.Text.Json.targets": {} + } + }, + "System.Threading.Channels/4.5.0": { + "type": "package", + "compile": { + "lib/netcoreapp2.1/System.Threading.Channels.dll": {} + }, + "runtime": { + "lib/netcoreapp2.1/System.Threading.Channels.dll": {} + } + }, + "System.Threading.Tasks/4.3.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + }, + "compile": { + "ref/netstandard1.3/System.Threading.Tasks.dll": {} + } + }, + "Haoliang.Core/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v6.0", + "dependencies": { + "BCrypt.Net-Next": "4.0.3", + "Haoliang.Models": "1.0.0", + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.AspNetCore.SignalR": "1.1.0", + "Microsoft.Extensions.Caching.Memory": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.IdentityModel.Tokens": "6.26.0", + "System.IdentityModel.Tokens.Jwt": "6.26.0" + }, + "compile": { + "bin/placeholder/Haoliang.Core.dll": {} + }, + "runtime": { + "bin/placeholder/Haoliang.Core.dll": {} + } + }, + "Haoliang.Models/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v6.0", + "compile": { "bin/placeholder/Haoliang.Models.dll": {} }, "runtime": { @@ -249,6 +982,389 @@ } }, "libraries": { + "BCrypt.Net-Next/4.0.3": { + "sha512": "W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==", + "type": "package", + "path": "bcrypt.net-next/4.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "bcrypt.net-next.4.0.3.nupkg.sha512", + "bcrypt.net-next.nuspec", + "ico.png", + "lib/net20/BCrypt.Net-Next.dll", + "lib/net20/BCrypt.Net-Next.xml", + "lib/net35/BCrypt.Net-Next.dll", + "lib/net35/BCrypt.Net-Next.xml", + "lib/net462/BCrypt.Net-Next.dll", + "lib/net462/BCrypt.Net-Next.xml", + "lib/net472/BCrypt.Net-Next.dll", + "lib/net472/BCrypt.Net-Next.xml", + "lib/net48/BCrypt.Net-Next.dll", + "lib/net48/BCrypt.Net-Next.xml", + "lib/net5.0/BCrypt.Net-Next.dll", + "lib/net5.0/BCrypt.Net-Next.xml", + "lib/net6.0/BCrypt.Net-Next.dll", + "lib/net6.0/BCrypt.Net-Next.xml", + "lib/netstandard2.0/BCrypt.Net-Next.dll", + "lib/netstandard2.0/BCrypt.Net-Next.xml", + "lib/netstandard2.1/BCrypt.Net-Next.dll", + "lib/netstandard2.1/BCrypt.Net-Next.xml", + "readme.md" + ] + }, + "Humanizer.Core/2.14.1": { + "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "type": "package", + "path": "humanizer.core/2.14.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "humanizer.core.2.14.1.nupkg.sha512", + "humanizer.core.nuspec", + "lib/net6.0/Humanizer.dll", + "lib/net6.0/Humanizer.xml", + "lib/netstandard1.0/Humanizer.dll", + "lib/netstandard1.0/Humanizer.xml", + "lib/netstandard2.0/Humanizer.dll", + "lib/netstandard2.0/Humanizer.xml", + "logo.png" + ] + }, + "Microsoft.AspNetCore.Authentication.Abstractions/2.2.0": { + "sha512": "VloMLDJMf3n/9ic5lCBOa42IBYJgyB1JhzLsL68Zqg+2bEPWfGBj/xCJy/LrKTArN0coOcZp3wyVTZlx0y9pHQ==", + "type": "package", + "path": "microsoft.aspnetcore.authentication.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.xml", + "microsoft.aspnetcore.authentication.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.authentication.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Authorization/2.2.0": { + "sha512": "/L0W8H3jMYWyaeA9gBJqS/tSWBegP9aaTM0mjRhxTttBY9z4RVDRYJ2CwPAmAXIuPr3r1sOw+CS8jFVRGHRezQ==", + "type": "package", + "path": "microsoft.aspnetcore.authorization/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.xml", + "microsoft.aspnetcore.authorization.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.authorization.nuspec" + ] + }, + "Microsoft.AspNetCore.Authorization.Policy/2.2.0": { + "sha512": "aJCo6niDRKuNg2uS2WMEmhJTooQUGARhV2ENQ2tO5443zVHUo19MSgrgGo9FIrfD+4yKPF8Q+FF33WkWfPbyKw==", + "type": "package", + "path": "microsoft.aspnetcore.authorization.policy/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.xml", + "microsoft.aspnetcore.authorization.policy.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.authorization.policy.nuspec" + ] + }, + "Microsoft.AspNetCore.Connections.Abstractions/2.2.0": { + "sha512": "Aqr/16Cu5XmGv7mLKJvXRxhhd05UJ7cTTSaUV4MZ3ynAzfgWjsAdpIU8FWuxwAjmVdmI8oOWuVDrbs+sRkhKnA==", + "type": "package", + "path": "microsoft.aspnetcore.connections.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Connections.Abstractions.xml", + "microsoft.aspnetcore.connections.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.connections.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Hosting.Abstractions/2.2.0": { + "sha512": "ubycklv+ZY7Kutdwuy1W4upWcZ6VFR8WUXU7l7B2+mvbDBBPAcfpi+E+Y5GFe+Q157YfA3C49D2GCjAZc7Mobw==", + "type": "package", + "path": "microsoft.aspnetcore.hosting.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.xml", + "microsoft.aspnetcore.hosting.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.hosting.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.2.0": { + "sha512": "1PMijw8RMtuQF60SsD/JlKtVfvh4NORAhF4wjysdABhlhTrYmtgssqyncR0Stq5vqtjplZcj6kbT4LRTglt9IQ==", + "type": "package", + "path": "microsoft.aspnetcore.hosting.server.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.xml", + "microsoft.aspnetcore.hosting.server.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.hosting.server.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Http/2.2.0": { + "sha512": "YogBSMotWPAS/X5967pZ+yyWPQkThxhmzAwyCHCSSldzYBkW5W5d6oPfBaPqQOnSHYTpSOSOkpZoAce0vwb6+A==", + "type": "package", + "path": "microsoft.aspnetcore.http/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.xml", + "microsoft.aspnetcore.http.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Abstractions/2.2.0": { + "sha512": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "type": "package", + "path": "microsoft.aspnetcore.http.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.xml", + "microsoft.aspnetcore.http.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Connections/1.1.0": { + "sha512": "ZcwAM9rE5yjGC+vtiNAK0INybpKIqnvB+/rntZn2/CPtyiBAtovVrEp4UZOoC31zH5t0P78ix9gLNJzII/ODsA==", + "type": "package", + "path": "microsoft.aspnetcore.http.connections/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.dll", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Http.Connections.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.xml", + "microsoft.aspnetcore.http.connections.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.http.connections.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Connections.Common/1.1.0": { + "sha512": "mYk5QUUjyXQmlyDHWDjkLYDArt97plwe6KsDsNVhDEQ+HgZMKGjISyM6YSA7BERQNR25kXBTbIYfSy1vePGQgg==", + "type": "package", + "path": "microsoft.aspnetcore.http.connections.common/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Connections.Common.xml", + "microsoft.aspnetcore.http.connections.common.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.http.connections.common.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Extensions/2.2.0": { + "sha512": "2DgZ9rWrJtuR7RYiew01nGRzuQBDaGHGmK56Rk54vsLLsCdzuFUPqbDTJCS1qJQWTbmbIQ9wGIOjpxA1t0l7/w==", + "type": "package", + "path": "microsoft.aspnetcore.http.extensions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.xml", + "microsoft.aspnetcore.http.extensions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.extensions.nuspec" + ] + }, + "Microsoft.AspNetCore.Http.Features/2.2.0": { + "sha512": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "type": "package", + "path": "microsoft.aspnetcore.http.features/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.xml", + "microsoft.aspnetcore.http.features.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.http.features.nuspec" + ] + }, + "Microsoft.AspNetCore.Routing/2.2.0": { + "sha512": "jAhDBy0wryOnMhhZTtT9z63gJbvCzFuLm8yC6pHzuVu9ZD1dzg0ltxIwT4cfwuNkIL/TixdKsm3vpVOpG8euWQ==", + "type": "package", + "path": "microsoft.aspnetcore.routing/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.dll", + "lib/netcoreapp2.2/Microsoft.AspNetCore.Routing.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.xml", + "microsoft.aspnetcore.routing.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.routing.nuspec" + ] + }, + "Microsoft.AspNetCore.Routing.Abstractions/2.2.0": { + "sha512": "lRRaPN7jDlUCVCp9i0W+PB0trFaKB0bgMJD7hEJS9Uo4R9MXaMC8X2tJhPLmeVE3SGDdYI4QNKdVmhNvMJGgPQ==", + "type": "package", + "path": "microsoft.aspnetcore.routing.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.xml", + "microsoft.aspnetcore.routing.abstractions.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.routing.abstractions.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR/1.1.0": { + "sha512": "V5X5XkeAHaFyyBOGPrddVeqTNo6zRPJNS5PRhlzEyBXiNG9AtqUbMyWFdZahQyMiIWJau550z59A4kdC9g5I9A==", + "type": "package", + "path": "microsoft.aspnetcore.signalr/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.xml", + "microsoft.aspnetcore.signalr.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR.Common/1.1.0": { + "sha512": "TyLgQ4y4RVUIxiYFnHT181/rJ33/tL/NcBWC9BwLpulDt5/yGCG4EvsToZ49EBQ7256zj+R6OGw6JF+jj6MdPQ==", + "type": "package", + "path": "microsoft.aspnetcore.signalr.common/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.dll", + "lib/netcoreapp2.2/Microsoft.AspNetCore.SignalR.Common.xml", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Common.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Common.xml", + "microsoft.aspnetcore.signalr.common.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.common.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR.Core/1.1.0": { + "sha512": "mk69z50oFk2e89d3F/AfKeAvP3kvGG7MHG4ErydZiUd3ncSRq0kl0czq/COn/QVKYua9yGr2LIDwuR1C6/pu8Q==", + "type": "package", + "path": "microsoft.aspnetcore.signalr.core/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Core.xml", + "microsoft.aspnetcore.signalr.core.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.core.nuspec" + ] + }, + "Microsoft.AspNetCore.SignalR.Protocols.Json/1.1.0": { + "sha512": "BOsjatDJnvnnXCMajOlC0ISmiFnJi/EyJzMo0i//5fZJVCLrQ4fyV/HzrhhAhSJuwJOQDdDozKQ9MB9jHq84pg==", + "type": "package", + "path": "microsoft.aspnetcore.signalr.protocols.json/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.SignalR.Protocols.Json.xml", + "microsoft.aspnetcore.signalr.protocols.json.1.1.0.nupkg.sha512", + "microsoft.aspnetcore.signalr.protocols.json.nuspec" + ] + }, + "Microsoft.AspNetCore.WebSockets/2.2.0": { + "sha512": "ZpOcg2V0rCwU9ErfDb9y3Hcjoe7rU42XlmUS0mO4pVZQSgJVqR+DfyZtYd5LDa11F7bFNS2eezI9cBM3CmfGhw==", + "type": "package", + "path": "microsoft.aspnetcore.websockets/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.WebSockets.xml", + "microsoft.aspnetcore.websockets.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.websockets.nuspec" + ] + }, + "Microsoft.AspNetCore.WebUtilities/2.2.0": { + "sha512": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", + "type": "package", + "path": "microsoft.aspnetcore.webutilities/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll", + "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.xml", + "microsoft.aspnetcore.webutilities.2.2.0.nupkg.sha512", + "microsoft.aspnetcore.webutilities.nuspec" + ] + }, + "Microsoft.CSharp/4.5.0": { + "sha512": "kaj6Wb4qoMuH3HySFJhxwQfe8R/sJsNJnANrvv8WdFPMoNbKY5htfNscv+LHCu5ipz+49m2e+WQXpLXr9XYemQ==", + "type": "package", + "path": "microsoft.csharp/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/Microsoft.CSharp.dll", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.3/Microsoft.CSharp.dll", + "lib/netstandard2.0/Microsoft.CSharp.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/uap10.0.16299/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "microsoft.csharp.4.5.0.nupkg.sha512", + "microsoft.csharp.nuspec", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/Microsoft.CSharp.dll", + "ref/netcore50/Microsoft.CSharp.xml", + "ref/netcore50/de/Microsoft.CSharp.xml", + "ref/netcore50/es/Microsoft.CSharp.xml", + "ref/netcore50/fr/Microsoft.CSharp.xml", + "ref/netcore50/it/Microsoft.CSharp.xml", + "ref/netcore50/ja/Microsoft.CSharp.xml", + "ref/netcore50/ko/Microsoft.CSharp.xml", + "ref/netcore50/ru/Microsoft.CSharp.xml", + "ref/netcore50/zh-hans/Microsoft.CSharp.xml", + "ref/netcore50/zh-hant/Microsoft.CSharp.xml", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.0/Microsoft.CSharp.dll", + "ref/netstandard1.0/Microsoft.CSharp.xml", + "ref/netstandard1.0/de/Microsoft.CSharp.xml", + "ref/netstandard1.0/es/Microsoft.CSharp.xml", + "ref/netstandard1.0/fr/Microsoft.CSharp.xml", + "ref/netstandard1.0/it/Microsoft.CSharp.xml", + "ref/netstandard1.0/ja/Microsoft.CSharp.xml", + "ref/netstandard1.0/ko/Microsoft.CSharp.xml", + "ref/netstandard1.0/ru/Microsoft.CSharp.xml", + "ref/netstandard1.0/zh-hans/Microsoft.CSharp.xml", + "ref/netstandard1.0/zh-hant/Microsoft.CSharp.xml", + "ref/netstandard2.0/Microsoft.CSharp.dll", + "ref/netstandard2.0/Microsoft.CSharp.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/uap10.0.16299/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, "Microsoft.EntityFrameworkCore/7.0.2": { "sha512": "5QEspjTHk/cgM98AaB12mDXF7jlInlYhG0gxS6X8ZJ2rzmyIAsvYNEwoOUifd/gt5v5HblYClYfZ9YYIEjSkew==", "type": "package", @@ -292,6 +1408,21 @@ "microsoft.entityframeworkcore.analyzers.nuspec" ] }, + "Microsoft.EntityFrameworkCore.Design/7.0.2": { + "sha512": "gUUucCoJci8BBxOcU/XuuldUZpCo5Od8lwFEzZ5WywnvDfSmARnXNe97BpjL+JiBhSrnuTxW/wCJjWqPonXXHQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.design/7.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/net6.0/Microsoft.EntityFrameworkCore.Design.props", + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll", + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.xml", + "microsoft.entityframeworkcore.design.7.0.2.nupkg.sha512", + "microsoft.entityframeworkcore.design.nuspec" + ] + }, "Microsoft.EntityFrameworkCore.Relational/7.0.2": { "sha512": "TbTGOdaGtjps3GP7rLWXEXzmP+EXhV8AwPE/ov0QYhs5i5vKZX5ZpVLMnco2MeMtiPgLyxE7DhQT8s1wlu190g==", "type": "package", @@ -306,6 +1437,31 @@ "microsoft.entityframeworkcore.relational.nuspec" ] }, + "Microsoft.EntityFrameworkCore.Tools/7.0.2": { + "sha512": "0Jx9feeGsUUlI+PEFkADyfQrGU+UIYh9N1I8ZO6X5bjYSKL2V1empkGTupvfrI7S9h4uA7aY8GQpjkCmIep7dg==", + "type": "package", + "path": "microsoft.entityframeworkcore.tools/7.0.2", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "lib/net6.0/_._", + "microsoft.entityframeworkcore.tools.7.0.2.nupkg.sha512", + "microsoft.entityframeworkcore.tools.nuspec", + "tools/EntityFrameworkCore.PS2.psd1", + "tools/EntityFrameworkCore.PS2.psm1", + "tools/EntityFrameworkCore.psd1", + "tools/EntityFrameworkCore.psm1", + "tools/about_EntityFrameworkCore.help.txt", + "tools/init.ps1", + "tools/net461/any/ef.exe", + "tools/net461/win-arm64/ef.exe", + "tools/net461/win-x86/ef.exe", + "tools/netcoreapp2.0/any/ef.dll", + "tools/netcoreapp2.0/any/ef.runtimeconfig.json" + ] + }, "Microsoft.Extensions.Caching.Abstractions/7.0.0": { "sha512": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", "type": "package", @@ -445,6 +1601,60 @@ "useSharedDesignerContext.txt" ] }, + "Microsoft.Extensions.DependencyModel/7.0.0": { + "sha512": "oONNYd71J3LzkWc4fUHl3SvMfiQMYUCo/mDHDEu76hYYxdhdrPYv6fvGv9nnKVyhE9P0h20AU8RZB5OOWQcAXg==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net6.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net7.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net7.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.7.0.0.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.FileProviders.Abstractions/2.2.0": { + "sha512": "EcnaSsPTqx2MGnHrmWOD0ugbuuqVT8iICqSqPzi45V5/MA1LjUNb0kwgcxBGqizV1R+WeBK7/Gw25Jzkyk9bIw==", + "type": "package", + "path": "microsoft.extensions.fileproviders.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "microsoft.extensions.fileproviders.abstractions.2.2.0.nupkg.sha512", + "microsoft.extensions.fileproviders.abstractions.nuspec" + ] + }, + "Microsoft.Extensions.Hosting.Abstractions/2.2.0": { + "sha512": "+k4AEn68HOJat5gj1TWa6X28WlirNQO9sPIIeQbia+91n03esEtMSSoekSTpMjUzjqtJWQN3McVx0GvSPFHF/Q==", + "type": "package", + "path": "microsoft.extensions.hosting.abstractions/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "microsoft.extensions.hosting.abstractions.2.2.0.nupkg.sha512", + "microsoft.extensions.hosting.abstractions.nuspec" + ] + }, "Microsoft.Extensions.Logging/7.0.0": { "sha512": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "type": "package", @@ -544,6 +1754,19 @@ "useSharedDesignerContext.txt" ] }, + "Microsoft.Extensions.ObjectPool/2.2.0": { + "sha512": "gA8H7uQOnM5gb+L0uTNjViHYr+hRDqCdfugheGo/MxQnuHzmhhzCBTIPm19qL1z1Xe0NEMabfcOBGv9QghlZ8g==", + "type": "package", + "path": "microsoft.extensions.objectpool/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll", + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.xml", + "microsoft.extensions.objectpool.2.2.0.nupkg.sha512", + "microsoft.extensions.objectpool.nuspec" + ] + }, "Microsoft.Extensions.Options/7.0.0": { "sha512": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", "type": "package", @@ -600,6 +1823,148 @@ "useSharedDesignerContext.txt" ] }, + "Microsoft.IdentityModel.Abstractions/6.26.0": { + "sha512": "NHEnDBvLYqP81YWqKk1pJt0qSUmqobvFsRL/SR/H6x1jmQh2D1EcuHHhmfIzDnzaOlQJL9GeBDHykqHp0JGNCw==", + "type": "package", + "path": "microsoft.identitymodel.abstractions/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.Abstractions.dll", + "lib/net45/Microsoft.IdentityModel.Abstractions.xml", + "lib/net461/Microsoft.IdentityModel.Abstractions.dll", + "lib/net461/Microsoft.IdentityModel.Abstractions.xml", + "lib/net472/Microsoft.IdentityModel.Abstractions.dll", + "lib/net472/Microsoft.IdentityModel.Abstractions.xml", + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/net6.0/Microsoft.IdentityModel.Abstractions.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.xml", + "microsoft.identitymodel.abstractions.6.26.0.nupkg.sha512", + "microsoft.identitymodel.abstractions.nuspec" + ] + }, + "Microsoft.IdentityModel.JsonWebTokens/6.26.0": { + "sha512": "5S993Y51C6p3pQGcvJvUU4Bxq5H5tXGyAzvmXXZkELv8pSWVgbgVsQakGupjx6WLFRN+Y6clp9chVytynWYn5A==", + "type": "package", + "path": "microsoft.identitymodel.jsonwebtokens/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net45/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net461/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net461/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net472/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net472/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "microsoft.identitymodel.jsonwebtokens.6.26.0.nupkg.sha512", + "microsoft.identitymodel.jsonwebtokens.nuspec" + ] + }, + "Microsoft.IdentityModel.Logging/6.26.0": { + "sha512": "Svec5ltH4zz5ylAmFiHrUETLalw3d8siPbQ7+0H9GNGbZrVf5u7TaHpmDuJyb3EUiITfisD3vM83spsO/l1igA==", + "type": "package", + "path": "microsoft.identitymodel.logging/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.Logging.dll", + "lib/net45/Microsoft.IdentityModel.Logging.xml", + "lib/net461/Microsoft.IdentityModel.Logging.dll", + "lib/net461/Microsoft.IdentityModel.Logging.xml", + "lib/net472/Microsoft.IdentityModel.Logging.dll", + "lib/net472/Microsoft.IdentityModel.Logging.xml", + "lib/net6.0/Microsoft.IdentityModel.Logging.dll", + "lib/net6.0/Microsoft.IdentityModel.Logging.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Logging.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Logging.xml", + "microsoft.identitymodel.logging.6.26.0.nupkg.sha512", + "microsoft.identitymodel.logging.nuspec" + ] + }, + "Microsoft.IdentityModel.Tokens/6.26.0": { + "sha512": "mFNbROC89eap6GTqoYcInCiYsaV8sLxPsgCurQnJDcJoLBk7XoAJpBJae6rkj2VEzWqfErd4jlzaqqRI7wjGOQ==", + "type": "package", + "path": "microsoft.identitymodel.tokens/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/Microsoft.IdentityModel.Tokens.dll", + "lib/net45/Microsoft.IdentityModel.Tokens.xml", + "lib/net461/Microsoft.IdentityModel.Tokens.dll", + "lib/net461/Microsoft.IdentityModel.Tokens.xml", + "lib/net472/Microsoft.IdentityModel.Tokens.dll", + "lib/net472/Microsoft.IdentityModel.Tokens.xml", + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll", + "lib/net6.0/Microsoft.IdentityModel.Tokens.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.xml", + "microsoft.identitymodel.tokens.6.26.0.nupkg.sha512", + "microsoft.identitymodel.tokens.nuspec" + ] + }, + "Microsoft.Net.Http.Headers/2.2.0": { + "sha512": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", + "type": "package", + "path": "microsoft.net.http.headers/2.2.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll", + "lib/netstandard2.0/Microsoft.Net.Http.Headers.xml", + "microsoft.net.http.headers.2.2.0.nupkg.sha512", + "microsoft.net.http.headers.nuspec" + ] + }, + "Microsoft.NETCore.Platforms/2.0.0": { + "sha512": "VdLJOCXhZaEMY7Hm2GKiULmn7IEPFE4XC5LPSfBVCUIA8YLZVh846gtfBJalsPQF2PlzdD7ecX7DZEulJ402ZQ==", + "type": "package", + "path": "microsoft.netcore.platforms/2.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netstandard1.0/_._", + "microsoft.netcore.platforms.2.0.0.nupkg.sha512", + "microsoft.netcore.platforms.nuspec", + "runtime.json", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "Microsoft.NETCore.Targets/1.1.0": { + "sha512": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", + "type": "package", + "path": "microsoft.netcore.targets/1.1.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "microsoft.netcore.targets.1.1.0.nupkg.sha512", + "microsoft.netcore.targets.nuspec", + "runtime.json" + ] + }, + "Mono.TextTemplating/2.2.1": { + "sha512": "KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==", + "type": "package", + "path": "mono.texttemplating/2.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net472/Mono.TextTemplating.dll", + "lib/netstandard2.0/Mono.TextTemplating.dll", + "mono.texttemplating.2.2.1.nupkg.sha512", + "mono.texttemplating.nuspec" + ] + }, "MySqlConnector/2.2.5": { "sha512": "6sinY78RvryhHwpup3awdjYO7d5hhWahb5p/1VDODJhSxJggV/sBbYuKK5IQF9TuzXABiddqUbmRfM884tqA3Q==", "type": "package", @@ -627,6 +1992,36 @@ "mysqlconnector.nuspec" ] }, + "Newtonsoft.Json/11.0.2": { + "sha512": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ==", + "type": "package", + "path": "newtonsoft.json/11.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "lib/portable-net40+sl5+win8+wp8+wpa81/Newtonsoft.Json.dll", + "lib/portable-net40+sl5+win8+wp8+wpa81/Newtonsoft.Json.xml", + "lib/portable-net45+win8+wp8+wpa81/Newtonsoft.Json.dll", + "lib/portable-net45+win8+wp8+wpa81/Newtonsoft.Json.xml", + "newtonsoft.json.11.0.2.nupkg.sha512", + "newtonsoft.json.nuspec" + ] + }, "Pomelo.EntityFrameworkCore.MySql/7.0.0": { "sha512": "Qk5WB/skSZet5Yrz6AN2ywjZaB1pxfAmvQ+5I4khTkLwwIamI4QJoH2NphCWLFQL+2ar8HvsNCTmwYk0qhqL0w==", "type": "package", @@ -644,6 +2039,509 @@ "pomelo.entityframeworkcore.mysql.nuspec" ] }, + "System.Buffers/4.5.0": { + "sha512": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==", + "type": "package", + "path": "system.buffers/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.1/System.Buffers.dll", + "lib/netstandard1.1/System.Buffers.xml", + "lib/netstandard2.0/System.Buffers.dll", + "lib/netstandard2.0/System.Buffers.xml", + "lib/uap10.0.16299/_._", + "ref/net45/System.Buffers.dll", + "ref/net45/System.Buffers.xml", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.1/System.Buffers.dll", + "ref/netstandard1.1/System.Buffers.xml", + "ref/netstandard2.0/System.Buffers.dll", + "ref/netstandard2.0/System.Buffers.xml", + "ref/uap10.0.16299/_._", + "system.buffers.4.5.0.nupkg.sha512", + "system.buffers.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.CodeDom/4.4.0": { + "sha512": "2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA==", + "type": "package", + "path": "system.codedom/4.4.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.dll", + "ref/net461/System.CodeDom.dll", + "ref/net461/System.CodeDom.xml", + "ref/netstandard2.0/System.CodeDom.dll", + "ref/netstandard2.0/System.CodeDom.xml", + "system.codedom.4.4.0.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.IdentityModel.Tokens.Jwt/6.26.0": { + "sha512": "GT6imbntzCpoGHTRFUa98TPCF9PTnzV1v5KiTj9sT5ZmeYZErNA5ks5VDvYBaOC59y3dQ78IsMzEJm+XrxDk6w==", + "type": "package", + "path": "system.identitymodel.tokens.jwt/6.26.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net45/System.IdentityModel.Tokens.Jwt.dll", + "lib/net45/System.IdentityModel.Tokens.Jwt.xml", + "lib/net461/System.IdentityModel.Tokens.Jwt.dll", + "lib/net461/System.IdentityModel.Tokens.Jwt.xml", + "lib/net472/System.IdentityModel.Tokens.Jwt.dll", + "lib/net472/System.IdentityModel.Tokens.Jwt.xml", + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/net6.0/System.IdentityModel.Tokens.Jwt.xml", + "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.xml", + "system.identitymodel.tokens.jwt.6.26.0.nupkg.sha512", + "system.identitymodel.tokens.jwt.nuspec" + ] + }, + "System.IO/4.3.0": { + "sha512": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "type": "package", + "path": "system.io/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.IO.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.IO.dll", + "ref/netcore50/System.IO.dll", + "ref/netcore50/System.IO.xml", + "ref/netcore50/de/System.IO.xml", + "ref/netcore50/es/System.IO.xml", + "ref/netcore50/fr/System.IO.xml", + "ref/netcore50/it/System.IO.xml", + "ref/netcore50/ja/System.IO.xml", + "ref/netcore50/ko/System.IO.xml", + "ref/netcore50/ru/System.IO.xml", + "ref/netcore50/zh-hans/System.IO.xml", + "ref/netcore50/zh-hant/System.IO.xml", + "ref/netstandard1.0/System.IO.dll", + "ref/netstandard1.0/System.IO.xml", + "ref/netstandard1.0/de/System.IO.xml", + "ref/netstandard1.0/es/System.IO.xml", + "ref/netstandard1.0/fr/System.IO.xml", + "ref/netstandard1.0/it/System.IO.xml", + "ref/netstandard1.0/ja/System.IO.xml", + "ref/netstandard1.0/ko/System.IO.xml", + "ref/netstandard1.0/ru/System.IO.xml", + "ref/netstandard1.0/zh-hans/System.IO.xml", + "ref/netstandard1.0/zh-hant/System.IO.xml", + "ref/netstandard1.3/System.IO.dll", + "ref/netstandard1.3/System.IO.xml", + "ref/netstandard1.3/de/System.IO.xml", + "ref/netstandard1.3/es/System.IO.xml", + "ref/netstandard1.3/fr/System.IO.xml", + "ref/netstandard1.3/it/System.IO.xml", + "ref/netstandard1.3/ja/System.IO.xml", + "ref/netstandard1.3/ko/System.IO.xml", + "ref/netstandard1.3/ru/System.IO.xml", + "ref/netstandard1.3/zh-hans/System.IO.xml", + "ref/netstandard1.3/zh-hant/System.IO.xml", + "ref/netstandard1.5/System.IO.dll", + "ref/netstandard1.5/System.IO.xml", + "ref/netstandard1.5/de/System.IO.xml", + "ref/netstandard1.5/es/System.IO.xml", + "ref/netstandard1.5/fr/System.IO.xml", + "ref/netstandard1.5/it/System.IO.xml", + "ref/netstandard1.5/ja/System.IO.xml", + "ref/netstandard1.5/ko/System.IO.xml", + "ref/netstandard1.5/ru/System.IO.xml", + "ref/netstandard1.5/zh-hans/System.IO.xml", + "ref/netstandard1.5/zh-hant/System.IO.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.io.4.3.0.nupkg.sha512", + "system.io.nuspec" + ] + }, + "System.IO.Pipelines/4.5.2": { + "sha512": "NOC/SO4gSX6t0tB25xxDPqPEzkksuzW7NVFBTQGAkjXXUPQl7ZtyE83T7tUCP2huFBbPombfCKvq1Ox1aG8D9w==", + "type": "package", + "path": "system.io.pipelines/4.5.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/System.IO.Pipelines.dll", + "lib/netcoreapp2.1/System.IO.Pipelines.xml", + "lib/netstandard1.3/System.IO.Pipelines.dll", + "lib/netstandard1.3/System.IO.Pipelines.xml", + "lib/netstandard2.0/System.IO.Pipelines.dll", + "lib/netstandard2.0/System.IO.Pipelines.xml", + "ref/netstandard1.3/System.IO.Pipelines.dll", + "system.io.pipelines.4.5.2.nupkg.sha512", + "system.io.pipelines.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Net.WebSockets.WebSocketProtocol/4.5.1": { + "sha512": "FquLjdb/0CeMqb15u9Px6TwnyFl306WztKWu6sKKc5kWPYMdpi5BFEkdxzGoieYFp9UksyGwJnCw4KKAUfJjrw==", + "type": "package", + "path": "system.net.websockets.websocketprotocol/4.5.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/System.Net.WebSockets.WebSocketProtocol.dll", + "lib/netstandard2.0/System.Net.WebSockets.WebSocketProtocol.dll", + "ref/netstandard2.0/System.Net.WebSockets.WebSocketProtocol.dll", + "system.net.websockets.websocketprotocol.4.5.1.nupkg.sha512", + "system.net.websockets.websocketprotocol.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Reflection/4.3.0": { + "sha512": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "type": "package", + "path": "system.reflection/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Reflection.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Reflection.dll", + "ref/netcore50/System.Reflection.dll", + "ref/netcore50/System.Reflection.xml", + "ref/netcore50/de/System.Reflection.xml", + "ref/netcore50/es/System.Reflection.xml", + "ref/netcore50/fr/System.Reflection.xml", + "ref/netcore50/it/System.Reflection.xml", + "ref/netcore50/ja/System.Reflection.xml", + "ref/netcore50/ko/System.Reflection.xml", + "ref/netcore50/ru/System.Reflection.xml", + "ref/netcore50/zh-hans/System.Reflection.xml", + "ref/netcore50/zh-hant/System.Reflection.xml", + "ref/netstandard1.0/System.Reflection.dll", + "ref/netstandard1.0/System.Reflection.xml", + "ref/netstandard1.0/de/System.Reflection.xml", + "ref/netstandard1.0/es/System.Reflection.xml", + "ref/netstandard1.0/fr/System.Reflection.xml", + "ref/netstandard1.0/it/System.Reflection.xml", + "ref/netstandard1.0/ja/System.Reflection.xml", + "ref/netstandard1.0/ko/System.Reflection.xml", + "ref/netstandard1.0/ru/System.Reflection.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.xml", + "ref/netstandard1.3/System.Reflection.dll", + "ref/netstandard1.3/System.Reflection.xml", + "ref/netstandard1.3/de/System.Reflection.xml", + "ref/netstandard1.3/es/System.Reflection.xml", + "ref/netstandard1.3/fr/System.Reflection.xml", + "ref/netstandard1.3/it/System.Reflection.xml", + "ref/netstandard1.3/ja/System.Reflection.xml", + "ref/netstandard1.3/ko/System.Reflection.xml", + "ref/netstandard1.3/ru/System.Reflection.xml", + "ref/netstandard1.3/zh-hans/System.Reflection.xml", + "ref/netstandard1.3/zh-hant/System.Reflection.xml", + "ref/netstandard1.5/System.Reflection.dll", + "ref/netstandard1.5/System.Reflection.xml", + "ref/netstandard1.5/de/System.Reflection.xml", + "ref/netstandard1.5/es/System.Reflection.xml", + "ref/netstandard1.5/fr/System.Reflection.xml", + "ref/netstandard1.5/it/System.Reflection.xml", + "ref/netstandard1.5/ja/System.Reflection.xml", + "ref/netstandard1.5/ko/System.Reflection.xml", + "ref/netstandard1.5/ru/System.Reflection.xml", + "ref/netstandard1.5/zh-hans/System.Reflection.xml", + "ref/netstandard1.5/zh-hant/System.Reflection.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.reflection.4.3.0.nupkg.sha512", + "system.reflection.nuspec" + ] + }, + "System.Reflection.Emit/4.3.0": { + "sha512": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "type": "package", + "path": "system.reflection.emit/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/monotouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.dll", + "lib/netstandard1.3/System.Reflection.Emit.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/net45/_._", + "ref/netstandard1.1/System.Reflection.Emit.dll", + "ref/netstandard1.1/System.Reflection.Emit.xml", + "ref/netstandard1.1/de/System.Reflection.Emit.xml", + "ref/netstandard1.1/es/System.Reflection.Emit.xml", + "ref/netstandard1.1/fr/System.Reflection.Emit.xml", + "ref/netstandard1.1/it/System.Reflection.Emit.xml", + "ref/netstandard1.1/ja/System.Reflection.Emit.xml", + "ref/netstandard1.1/ko/System.Reflection.Emit.xml", + "ref/netstandard1.1/ru/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hans/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hant/System.Reflection.Emit.xml", + "ref/xamarinmac20/_._", + "system.reflection.emit.4.3.0.nupkg.sha512", + "system.reflection.emit.nuspec" + ] + }, + "System.Reflection.Emit.ILGeneration/4.3.0": { + "sha512": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "type": "package", + "path": "system.reflection.emit.ilgeneration/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.ILGeneration.dll", + "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll", + "lib/portable-net45+wp8/_._", + "lib/wp80/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.dll", + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/de/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/es/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/fr/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/it/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ja/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ko/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ru/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Emit.ILGeneration.xml", + "ref/portable-net45+wp8/_._", + "ref/wp80/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/_._", + "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512", + "system.reflection.emit.ilgeneration.nuspec" + ] + }, + "System.Reflection.Primitives/4.3.0": { + "sha512": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "type": "package", + "path": "system.reflection.primitives/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Primitives.dll", + "ref/netcore50/System.Reflection.Primitives.xml", + "ref/netcore50/de/System.Reflection.Primitives.xml", + "ref/netcore50/es/System.Reflection.Primitives.xml", + "ref/netcore50/fr/System.Reflection.Primitives.xml", + "ref/netcore50/it/System.Reflection.Primitives.xml", + "ref/netcore50/ja/System.Reflection.Primitives.xml", + "ref/netcore50/ko/System.Reflection.Primitives.xml", + "ref/netcore50/ru/System.Reflection.Primitives.xml", + "ref/netcore50/zh-hans/System.Reflection.Primitives.xml", + "ref/netcore50/zh-hant/System.Reflection.Primitives.xml", + "ref/netstandard1.0/System.Reflection.Primitives.dll", + "ref/netstandard1.0/System.Reflection.Primitives.xml", + "ref/netstandard1.0/de/System.Reflection.Primitives.xml", + "ref/netstandard1.0/es/System.Reflection.Primitives.xml", + "ref/netstandard1.0/fr/System.Reflection.Primitives.xml", + "ref/netstandard1.0/it/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ja/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ko/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ru/System.Reflection.Primitives.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Primitives.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Primitives.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.reflection.primitives.4.3.0.nupkg.sha512", + "system.reflection.primitives.nuspec" + ] + }, + "System.Runtime/4.3.0": { + "sha512": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "type": "package", + "path": "system.runtime/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Runtime.dll", + "lib/portable-net45+win8+wp80+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Runtime.dll", + "ref/netcore50/System.Runtime.dll", + "ref/netcore50/System.Runtime.xml", + "ref/netcore50/de/System.Runtime.xml", + "ref/netcore50/es/System.Runtime.xml", + "ref/netcore50/fr/System.Runtime.xml", + "ref/netcore50/it/System.Runtime.xml", + "ref/netcore50/ja/System.Runtime.xml", + "ref/netcore50/ko/System.Runtime.xml", + "ref/netcore50/ru/System.Runtime.xml", + "ref/netcore50/zh-hans/System.Runtime.xml", + "ref/netcore50/zh-hant/System.Runtime.xml", + "ref/netstandard1.0/System.Runtime.dll", + "ref/netstandard1.0/System.Runtime.xml", + "ref/netstandard1.0/de/System.Runtime.xml", + "ref/netstandard1.0/es/System.Runtime.xml", + "ref/netstandard1.0/fr/System.Runtime.xml", + "ref/netstandard1.0/it/System.Runtime.xml", + "ref/netstandard1.0/ja/System.Runtime.xml", + "ref/netstandard1.0/ko/System.Runtime.xml", + "ref/netstandard1.0/ru/System.Runtime.xml", + "ref/netstandard1.0/zh-hans/System.Runtime.xml", + "ref/netstandard1.0/zh-hant/System.Runtime.xml", + "ref/netstandard1.2/System.Runtime.dll", + "ref/netstandard1.2/System.Runtime.xml", + "ref/netstandard1.2/de/System.Runtime.xml", + "ref/netstandard1.2/es/System.Runtime.xml", + "ref/netstandard1.2/fr/System.Runtime.xml", + "ref/netstandard1.2/it/System.Runtime.xml", + "ref/netstandard1.2/ja/System.Runtime.xml", + "ref/netstandard1.2/ko/System.Runtime.xml", + "ref/netstandard1.2/ru/System.Runtime.xml", + "ref/netstandard1.2/zh-hans/System.Runtime.xml", + "ref/netstandard1.2/zh-hant/System.Runtime.xml", + "ref/netstandard1.3/System.Runtime.dll", + "ref/netstandard1.3/System.Runtime.xml", + "ref/netstandard1.3/de/System.Runtime.xml", + "ref/netstandard1.3/es/System.Runtime.xml", + "ref/netstandard1.3/fr/System.Runtime.xml", + "ref/netstandard1.3/it/System.Runtime.xml", + "ref/netstandard1.3/ja/System.Runtime.xml", + "ref/netstandard1.3/ko/System.Runtime.xml", + "ref/netstandard1.3/ru/System.Runtime.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.xml", + "ref/netstandard1.5/System.Runtime.dll", + "ref/netstandard1.5/System.Runtime.xml", + "ref/netstandard1.5/de/System.Runtime.xml", + "ref/netstandard1.5/es/System.Runtime.xml", + "ref/netstandard1.5/fr/System.Runtime.xml", + "ref/netstandard1.5/it/System.Runtime.xml", + "ref/netstandard1.5/ja/System.Runtime.xml", + "ref/netstandard1.5/ko/System.Runtime.xml", + "ref/netstandard1.5/ru/System.Runtime.xml", + "ref/netstandard1.5/zh-hans/System.Runtime.xml", + "ref/netstandard1.5/zh-hant/System.Runtime.xml", + "ref/portable-net45+win8+wp80+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.runtime.4.3.0.nupkg.sha512", + "system.runtime.nuspec" + ] + }, "System.Runtime.CompilerServices.Unsafe/6.0.0": { "sha512": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", "type": "package", @@ -669,6 +2567,371 @@ "useSharedDesignerContext.txt" ] }, + "System.Security.Cryptography.Cng/4.5.0": { + "sha512": "WG3r7EyjUe9CMPFSs6bty5doUqT+q9pbI80hlNzo2SkPkZ4VTuZkGWjpp77JB8+uaL4DFPRdBsAY+DX3dBK92A==", + "type": "package", + "path": "system.security.cryptography.cng/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.Cng.dll", + "lib/net461/System.Security.Cryptography.Cng.dll", + "lib/net462/System.Security.Cryptography.Cng.dll", + "lib/net47/System.Security.Cryptography.Cng.dll", + "lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll", + "lib/netstandard1.3/System.Security.Cryptography.Cng.dll", + "lib/netstandard1.4/System.Security.Cryptography.Cng.dll", + "lib/netstandard1.6/System.Security.Cryptography.Cng.dll", + "lib/netstandard2.0/System.Security.Cryptography.Cng.dll", + "lib/uap10.0.16299/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.Cng.dll", + "ref/net461/System.Security.Cryptography.Cng.dll", + "ref/net461/System.Security.Cryptography.Cng.xml", + "ref/net462/System.Security.Cryptography.Cng.dll", + "ref/net462/System.Security.Cryptography.Cng.xml", + "ref/net47/System.Security.Cryptography.Cng.dll", + "ref/net47/System.Security.Cryptography.Cng.xml", + "ref/netcoreapp2.0/System.Security.Cryptography.Cng.dll", + "ref/netcoreapp2.0/System.Security.Cryptography.Cng.xml", + "ref/netcoreapp2.1/System.Security.Cryptography.Cng.dll", + "ref/netcoreapp2.1/System.Security.Cryptography.Cng.xml", + "ref/netstandard1.3/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.4/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.6/System.Security.Cryptography.Cng.dll", + "ref/netstandard2.0/System.Security.Cryptography.Cng.dll", + "ref/netstandard2.0/System.Security.Cryptography.Cng.xml", + "ref/uap10.0.16299/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/win/lib/net46/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net461/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net462/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net47/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netcoreapp2.0/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netcoreapp2.1/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netstandard1.4/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/uap10.0.16299/_._", + "system.security.cryptography.cng.4.5.0.nupkg.sha512", + "system.security.cryptography.cng.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Security.Principal.Windows/4.5.0": { + "sha512": "U77HfRXlZlOeIXd//Yoj6Jnk8AXlbeisf1oq1os+hxOGVnuG+lGSfGqTwTZBoORFF6j/0q7HXIl8cqwQ9aUGqQ==", + "type": "package", + "path": "system.security.principal.windows/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net46/System.Security.Principal.Windows.dll", + "lib/net461/System.Security.Principal.Windows.dll", + "lib/netstandard1.3/System.Security.Principal.Windows.dll", + "lib/netstandard2.0/System.Security.Principal.Windows.dll", + "lib/uap10.0.16299/_._", + "ref/net46/System.Security.Principal.Windows.dll", + "ref/net461/System.Security.Principal.Windows.dll", + "ref/net461/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/System.Security.Principal.Windows.dll", + "ref/netstandard1.3/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/de/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/es/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/fr/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/it/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/ja/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/ko/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/ru/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/zh-hans/System.Security.Principal.Windows.xml", + "ref/netstandard1.3/zh-hant/System.Security.Principal.Windows.xml", + "ref/netstandard2.0/System.Security.Principal.Windows.dll", + "ref/netstandard2.0/System.Security.Principal.Windows.xml", + "ref/uap10.0.16299/_._", + "runtimes/unix/lib/netcoreapp2.0/System.Security.Principal.Windows.dll", + "runtimes/win/lib/net46/System.Security.Principal.Windows.dll", + "runtimes/win/lib/net461/System.Security.Principal.Windows.dll", + "runtimes/win/lib/netcoreapp2.0/System.Security.Principal.Windows.dll", + "runtimes/win/lib/netstandard1.3/System.Security.Principal.Windows.dll", + "runtimes/win/lib/uap10.0.16299/_._", + "system.security.principal.windows.4.5.0.nupkg.sha512", + "system.security.principal.windows.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Text.Encoding/4.3.0": { + "sha512": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "type": "package", + "path": "system.text.encoding/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Text.Encoding.dll", + "ref/netcore50/System.Text.Encoding.xml", + "ref/netcore50/de/System.Text.Encoding.xml", + "ref/netcore50/es/System.Text.Encoding.xml", + "ref/netcore50/fr/System.Text.Encoding.xml", + "ref/netcore50/it/System.Text.Encoding.xml", + "ref/netcore50/ja/System.Text.Encoding.xml", + "ref/netcore50/ko/System.Text.Encoding.xml", + "ref/netcore50/ru/System.Text.Encoding.xml", + "ref/netcore50/zh-hans/System.Text.Encoding.xml", + "ref/netcore50/zh-hant/System.Text.Encoding.xml", + "ref/netstandard1.0/System.Text.Encoding.dll", + "ref/netstandard1.0/System.Text.Encoding.xml", + "ref/netstandard1.0/de/System.Text.Encoding.xml", + "ref/netstandard1.0/es/System.Text.Encoding.xml", + "ref/netstandard1.0/fr/System.Text.Encoding.xml", + "ref/netstandard1.0/it/System.Text.Encoding.xml", + "ref/netstandard1.0/ja/System.Text.Encoding.xml", + "ref/netstandard1.0/ko/System.Text.Encoding.xml", + "ref/netstandard1.0/ru/System.Text.Encoding.xml", + "ref/netstandard1.0/zh-hans/System.Text.Encoding.xml", + "ref/netstandard1.0/zh-hant/System.Text.Encoding.xml", + "ref/netstandard1.3/System.Text.Encoding.dll", + "ref/netstandard1.3/System.Text.Encoding.xml", + "ref/netstandard1.3/de/System.Text.Encoding.xml", + "ref/netstandard1.3/es/System.Text.Encoding.xml", + "ref/netstandard1.3/fr/System.Text.Encoding.xml", + "ref/netstandard1.3/it/System.Text.Encoding.xml", + "ref/netstandard1.3/ja/System.Text.Encoding.xml", + "ref/netstandard1.3/ko/System.Text.Encoding.xml", + "ref/netstandard1.3/ru/System.Text.Encoding.xml", + "ref/netstandard1.3/zh-hans/System.Text.Encoding.xml", + "ref/netstandard1.3/zh-hant/System.Text.Encoding.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.text.encoding.4.3.0.nupkg.sha512", + "system.text.encoding.nuspec" + ] + }, + "System.Text.Encodings.Web/7.0.0": { + "sha512": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==", + "type": "package", + "path": "system.text.encodings.web/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Text.Encodings.Web.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Text.Encodings.Web.targets", + "lib/net462/System.Text.Encodings.Web.dll", + "lib/net462/System.Text.Encodings.Web.xml", + "lib/net6.0/System.Text.Encodings.Web.dll", + "lib/net6.0/System.Text.Encodings.Web.xml", + "lib/net7.0/System.Text.Encodings.Web.dll", + "lib/net7.0/System.Text.Encodings.Web.xml", + "lib/netstandard2.0/System.Text.Encodings.Web.dll", + "lib/netstandard2.0/System.Text.Encodings.Web.xml", + "runtimes/browser/lib/net6.0/System.Text.Encodings.Web.dll", + "runtimes/browser/lib/net6.0/System.Text.Encodings.Web.xml", + "runtimes/browser/lib/net7.0/System.Text.Encodings.Web.dll", + "runtimes/browser/lib/net7.0/System.Text.Encodings.Web.xml", + "system.text.encodings.web.7.0.0.nupkg.sha512", + "system.text.encodings.web.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Text.Json/7.0.0": { + "sha512": "DaGSsVqKsn/ia6RG8frjwmJonfos0srquhw09TlT8KRw5I43E+4gs+/bZj4K0vShJ5H9imCuXupb4RmS+dBy3w==", + "type": "package", + "path": "system.text.json/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "buildTransitive/net461/System.Text.Json.targets", + "buildTransitive/net462/System.Text.Json.targets", + "buildTransitive/net6.0/System.Text.Json.targets", + "buildTransitive/netcoreapp2.0/System.Text.Json.targets", + "buildTransitive/netstandard2.0/System.Text.Json.targets", + "lib/net462/System.Text.Json.dll", + "lib/net462/System.Text.Json.xml", + "lib/net6.0/System.Text.Json.dll", + "lib/net6.0/System.Text.Json.xml", + "lib/net7.0/System.Text.Json.dll", + "lib/net7.0/System.Text.Json.xml", + "lib/netstandard2.0/System.Text.Json.dll", + "lib/netstandard2.0/System.Text.Json.xml", + "system.text.json.7.0.0.nupkg.sha512", + "system.text.json.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Threading.Channels/4.5.0": { + "sha512": "MEH06N0rIGmRT4LOKQ2BmUO0IxfvmIY/PaouSq+DFQku72OL8cxfw8W99uGpTCFf2vx2QHLRSh374iSM3asdTA==", + "type": "package", + "path": "system.threading.channels/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/System.Threading.Channels.dll", + "lib/netcoreapp2.1/System.Threading.Channels.xml", + "lib/netstandard1.3/System.Threading.Channels.dll", + "lib/netstandard1.3/System.Threading.Channels.xml", + "lib/netstandard2.0/System.Threading.Channels.dll", + "lib/netstandard2.0/System.Threading.Channels.xml", + "system.threading.channels.4.5.0.nupkg.sha512", + "system.threading.channels.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Threading.Tasks/4.3.0": { + "sha512": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "type": "package", + "path": "system.threading.tasks/4.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Threading.Tasks.dll", + "ref/netcore50/System.Threading.Tasks.xml", + "ref/netcore50/de/System.Threading.Tasks.xml", + "ref/netcore50/es/System.Threading.Tasks.xml", + "ref/netcore50/fr/System.Threading.Tasks.xml", + "ref/netcore50/it/System.Threading.Tasks.xml", + "ref/netcore50/ja/System.Threading.Tasks.xml", + "ref/netcore50/ko/System.Threading.Tasks.xml", + "ref/netcore50/ru/System.Threading.Tasks.xml", + "ref/netcore50/zh-hans/System.Threading.Tasks.xml", + "ref/netcore50/zh-hant/System.Threading.Tasks.xml", + "ref/netstandard1.0/System.Threading.Tasks.dll", + "ref/netstandard1.0/System.Threading.Tasks.xml", + "ref/netstandard1.0/de/System.Threading.Tasks.xml", + "ref/netstandard1.0/es/System.Threading.Tasks.xml", + "ref/netstandard1.0/fr/System.Threading.Tasks.xml", + "ref/netstandard1.0/it/System.Threading.Tasks.xml", + "ref/netstandard1.0/ja/System.Threading.Tasks.xml", + "ref/netstandard1.0/ko/System.Threading.Tasks.xml", + "ref/netstandard1.0/ru/System.Threading.Tasks.xml", + "ref/netstandard1.0/zh-hans/System.Threading.Tasks.xml", + "ref/netstandard1.0/zh-hant/System.Threading.Tasks.xml", + "ref/netstandard1.3/System.Threading.Tasks.dll", + "ref/netstandard1.3/System.Threading.Tasks.xml", + "ref/netstandard1.3/de/System.Threading.Tasks.xml", + "ref/netstandard1.3/es/System.Threading.Tasks.xml", + "ref/netstandard1.3/fr/System.Threading.Tasks.xml", + "ref/netstandard1.3/it/System.Threading.Tasks.xml", + "ref/netstandard1.3/ja/System.Threading.Tasks.xml", + "ref/netstandard1.3/ko/System.Threading.Tasks.xml", + "ref/netstandard1.3/ru/System.Threading.Tasks.xml", + "ref/netstandard1.3/zh-hans/System.Threading.Tasks.xml", + "ref/netstandard1.3/zh-hant/System.Threading.Tasks.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.threading.tasks.4.3.0.nupkg.sha512", + "system.threading.tasks.nuspec" + ] + }, "Haoliang.Core/1.0.0": { "type": "project", "path": "../Haoliang.Core/Haoliang.Core.csproj", @@ -684,7 +2947,10 @@ "net6.0": [ "Haoliang.Core >= 1.0.0", "Haoliang.Models >= 1.0.0", - "Pomelo.EntityFrameworkCore.MySql >= 6.0.32" + "Microsoft.EntityFrameworkCore >= 7.0.2", + "Microsoft.EntityFrameworkCore.Design >= 7.0.2", + "Microsoft.EntityFrameworkCore.Tools >= 7.0.2", + "Pomelo.EntityFrameworkCore.MySql >= 7.0.0" ] }, "packageFolders": { @@ -731,9 +2997,21 @@ "net6.0": { "targetAlias": "net6.0", "dependencies": { + "Microsoft.EntityFrameworkCore": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Design": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Tools": { + "target": "Package", + "version": "[7.0.2, )" + }, "Pomelo.EntityFrameworkCore.MySql": { "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.0, )" } }, "imports": [ @@ -754,17 +3032,5 @@ "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/6.0.136/RuntimeIdentifierGraph.json" } } - }, - "logs": [ - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Data depends on Pomelo.EntityFrameworkCore.MySql (>= 6.0.32) but Pomelo.EntityFrameworkCore.MySql 6.0.32 was not found. An approximate best match of Pomelo.EntityFrameworkCore.MySql 7.0.0 was resolved.", - "libraryId": "Pomelo.EntityFrameworkCore.MySql", - "targetGraphs": [ - "net6.0" - ] - } - ] + } } \ No newline at end of file diff --git a/Haoliang.Data/obj/project.nuget.cache b/Haoliang.Data/obj/project.nuget.cache index 74bb8c2..5cc6932 100644 --- a/Haoliang.Data/obj/project.nuget.cache +++ b/Haoliang.Data/obj/project.nuget.cache @@ -1,36 +1,81 @@ { "version": 2, - "dgSpecHash": "E3CYuztN82DeQmMlhzdS0AOTV0Soq7e+SryJHxeC7I9EIVSu1zIPNmSyI0D3F3vkphX4YtirNYarYNelXfdQ5Q==", + "dgSpecHash": "n+xdRAhcNg3J/OdmEBMOpDsgnuQbCY8EtwKbUclpRKp0/OYy4jdcYs5E4vglBVc6KURjabQne39GuK3NzrCrLA==", "success": true, "projectFilePath": "/root/opencode/haoliang/Haoliang.Data/Haoliang.Data.csproj", "expectedPackageFiles": [ + "/root/.nuget/packages/bcrypt.net-next/4.0.3/bcrypt.net-next.4.0.3.nupkg.sha512", + "/root/.nuget/packages/humanizer.core/2.14.1/humanizer.core.2.14.1.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.aspnetcore.authorization/2.2.0/microsoft.aspnetcore.authorization.2.2.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.aspnetcore.connections.abstractions/2.2.0/microsoft.aspnetcore.connections.abstractions.2.2.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.aspnetcore.hosting.server.abstractions/2.2.0/microsoft.aspnetcore.hosting.server.abstractions.2.2.0.nupkg.sha512", + "/root/.nuget/packages/microsoft.aspnetcore.http/2.2.0/microsoft.aspnetcore.http.2.2.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.aspnetcore.http.connections/1.1.0/microsoft.aspnetcore.http.connections.1.1.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.aspnetcore.http.extensions/2.2.0/microsoft.aspnetcore.http.extensions.2.2.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.aspnetcore.routing/2.2.0/microsoft.aspnetcore.routing.2.2.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.aspnetcore.signalr/1.1.0/microsoft.aspnetcore.signalr.1.1.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/microsoft.aspnetcore.signalr.core/1.1.0/microsoft.aspnetcore.signalr.core.1.1.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/microsoft.aspnetcore.websockets/2.2.0/microsoft.aspnetcore.websockets.2.2.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.runtime.compilerservices.unsafe/6.0.0/system.runtime.compilerservices.unsafe.6.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": [ - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Data depends on Pomelo.EntityFrameworkCore.MySql (>= 6.0.32) but Pomelo.EntityFrameworkCore.MySql 6.0.32 was not found. An approximate best match of Pomelo.EntityFrameworkCore.MySql 7.0.0 was resolved.", - "libraryId": "Pomelo.EntityFrameworkCore.MySql", - "targetGraphs": [ - "net6.0" - ] - } - ] + "logs": [] } \ No newline at end of file diff --git a/Haoliang.Models/DataCollection/AdditionalCollectionModels.cs b/Haoliang.Models/DataCollection/AdditionalCollectionModels.cs new file mode 100644 index 0000000..e4c70d9 --- /dev/null +++ b/Haoliang.Models/DataCollection/AdditionalCollectionModels.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; + +namespace Haoliang.Models.DataCollection +{ + public class CollectionTask + { + public int Id { get; set; } + public int DeviceId { get; set; } + public string TaskName { get; set; } + public string Status { get; set; } // Pending, Running, Completed, Failed + public DateTime ScheduledTime { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } + public bool IsSuccess { get; set; } + public string ErrorMessage { get; set; } + public int RetryCount { get; set; } + public DateTime CreatedAt { get; set; } + } + + public class CollectionResult + { + public int Id { get; set; } + public int DeviceId { get; set; } + public bool IsSuccess { get; set; } + public string RawJson { get; set; } + public string ParsedData { get; set; } + public DateTime CollectionTime { get; set; } + public long? ResponseTime { get; set; } + public int? DataSize { get; set; } + public string ErrorMessage { get; set; } + public DateTime CreatedAt { get; set; } + } + + public class CollectionLog + { + public int Id { get; set; } + public int DeviceId { get; set; } + public LogLevel LogLevel { get; set; } + public string LogCategory { get; set; } + public string LogMessage { get; set; } + public string LogData { get; set; } + public DateTime LogTime { get; set; } + public DateTime CreatedAt { get; set; } + } + + public class PingResult + { + public bool IsSuccess { get; set; } + public int PingTimeMs { get; set; } + public string ErrorMessage { get; set; } + public DateTime Timestamp { get; set; } + } + + public class DeviceStatus + { + public int Id { get; set; } + public int DeviceId { get; set; } + public string Status { get; set; } + public bool IsRunning { get; set; } + public string NCProgram { get; set; } + public int CumulativeCount { get; set; } + public string OperatingMode { get; set; } + public DateTime RecordTime { get; set; } + } + + public class TagData + { + public string Id { get; set; } + public string Desc { get; set; } + public string Quality { get; set; } + public object Value { get; set; } + public DateTime Time { get; set; } + } + + public class CollectionStatistics + { + public DateTime Date { get; set; } + public int TotalAttempts { get; set; } + public int SuccessCount { get; set; } + public int FailedCount { get; set; } + public decimal SuccessRate { get; set; } + public int DeviceCount { get; set; } + public int OnlineDeviceCount { get; set; } + public long TotalDataSize { get; set; } + public TimeSpan? AverageResponseTime { get; set; } + } + + public interface ICachingService + { + Task GetAsync(string key); + Task SetAsync(string key, T value, TimeSpan? expiration = null); + Task RemoveAsync(string key); + Task ExistsAsync(string key); + Task ClearAsync(); + Task GetOrCreateAsync(string key, Func> factory, TimeSpan? expiration = null); + Task> GetAllKeysAsync(); + Task RefreshAsync(string key); + } + + public interface ISchedulerService + { + Task StartSchedulerAsync(); + Task StopSchedulerAsync(); + Task ScheduleTaskAsync(ScheduledTask task); + Task RemoveTaskAsync(string taskId); + Task> GetAllScheduledTasksAsync(); + Task GetTaskByIdAsync(string taskId); + Task ExecuteTaskAsync(string taskId); + Task GetTaskExecutionResultAsync(string taskId); + Task IsTaskRunningAsync(string taskId); + } + + public interface IWebSocketAuthMiddleware + { + Task AuthenticateAsync(string connectionId, string token); + Task GetUserIdAsync(string connectionId); + Task GetConnectionIdAsync(string userId); + Task IsAuthenticatedAsync(string connectionId); + Task HasPermissionAsync(string connectionId, string permission); + } + + public class BackgroundTaskManager : ISchedulerService + { + // Implementation would go here - this is just a placeholder + public async Task StartSchedulerAsync() { await Task.CompletedTask; } + public async Task StopSchedulerAsync() { await Task.CompletedTask; } + public async Task ScheduleTaskAsync(ScheduledTask task) { await Task.CompletedTask; } + public async Task RemoveTaskAsync(string taskId) { return true; } + public async Task> GetAllScheduledTasksAsync() { return new List(); } + public async Task GetTaskByIdAsync(string taskId) { return null; } + public async Task ExecuteTaskAsync(string taskId) { await Task.CompletedTask; } + public async Task GetTaskExecutionResultAsync(string taskId) { return null; } + public async Task IsTaskRunningAsync(string taskId) { return false; } + } + + public class CacheManager : ICachingService + { + // Implementation would go here - this is just a placeholder + public async Task GetAsync(string key) { return default; } + public async Task SetAsync(string key, T value, TimeSpan? expiration = null) { await Task.CompletedTask; } + public async Task RemoveAsync(string key) { return true; } + public async Task ExistsAsync(string key) { return false; } + public async Task ClearAsync() { await Task.CompletedTask; } + public async Task GetOrCreateAsync(string key, Func> factory, TimeSpan? expiration = null) { return default; } + public async Task> GetAllKeysAsync() { return new List(); } + public async Task RefreshAsync(string key) { return true; } + } +} \ No newline at end of file diff --git a/Haoliang.Models/DataCollection/CollectionModels.cs b/Haoliang.Models/DataCollection/CollectionModels.cs index 7094907..cfd72cf 100644 --- a/Haoliang.Models/DataCollection/CollectionModels.cs +++ b/Haoliang.Models/DataCollection/CollectionModels.cs @@ -6,7 +6,7 @@ namespace Haoliang.Models.DataCollection { public class CollectionTask { - public int Id { get; set; } + public int TaskId { get; set; } public int DeviceId { get; set; } public string TaskName { get; set; } public string Status { get; set; } // Pending, Running, Completed, Failed @@ -19,6 +19,91 @@ namespace Haoliang.Models.DataCollection public DateTime CreatedAt { get; set; } } + public class CollectionResult + { + public int ResultId { get; set; } + public int TaskId { get; set; } + public int DeviceId { get; set; } + public bool IsSuccess { get; set; } + public string RawData { get; set; } + public string ParsedData { get; set; } + public DateTime CollectionTime { get; set; } + public int ResponseTimeMs { get; set; } + public string ErrorMessage { get; set; } + public Dictionary Metadata { get; set; } + } + + public class CollectionLog + { + public int LogId { get; set; } + public int DeviceId { get; set; } + public LogLevel LogLevel { get; set; } + public string Message { get; set; } + public string Exception { get; set; } + public DateTime Timestamp { get; set; } + public string Category { get; set; } + } + + public class PingResult + { + public bool IsSuccess { get; set; } + public int PingTimeMs { get; set; } + public string ErrorMessage { get; set; } + public DateTime Timestamp { get; set; } + } + + public class DeviceCurrentStatus + { + public int DeviceId { get; set; } + public string DeviceCode { get; set; } + public DeviceStatus Status { get; set; } + public string NCProgram { get; set; } + public int CumulativeCount { get; set; } + public DateTime RecordTime { get; set; } + public bool IsActive => Status == DeviceStatus.Online; + } + + public class CollectionHealth + { + public int TotalDevices { get; set; } + public int OnlineDevices { get; set; } + public int ActiveCollections { get; set; } + public double AverageResponseTime { get; set; } + public double SuccessRate { get; set; } + public Dictionary StatusCounts { get; set; } + } + + public class CollectionStatistics + { + public DateTime Date { get; set; } + public int TotalCollections { get; set; } + public int SuccessfulCollections { get; set; } + public int FailedCollections { get; set; } + public double AverageResponseTime { get; set; } + public Dictionary ErrorCounts { get; set; } + } + + public class DeviceStatistics + { + public int DeviceId { get; set; } + public string DeviceCode { get; set; } + public int TotalRunTime { get; set; } + public int TotalProductionCount { get; set; } + public double EfficiencyRate { get; set; } + public DateTime LastActiveTime { get; set; } + public Dictionary ProgramCounts { get; set; } + } + + public class TagData + { + public string Id { get; set; } + public string Description { get; set; } + public int Quality { get; set; } + public object Value { get; set; } + public DateTime Time { get; set; } + } +} + public class CollectionResult { public int Id { get; set; } diff --git a/Haoliang.Models/Haoliang.Models.csproj b/Haoliang.Models/Haoliang.Models.csproj index 06fa0d5..132c02c 100644 --- a/Haoliang.Models/Haoliang.Models.csproj +++ b/Haoliang.Models/Haoliang.Models.csproj @@ -6,17 +6,4 @@ enable - - - - - - - - - - - - - diff --git a/Haoliang.Models/Models/System/SystemConfigModels.cs b/Haoliang.Models/Models/System/SystemConfigModels.cs new file mode 100644 index 0000000..cab8650 --- /dev/null +++ b/Haoliang.Models/Models/System/SystemConfigModels.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using Haoliang.Models.Common; + +namespace Haoliang.Models.Models.System +{ + /// + /// Production target configuration + /// + public class ProductionTargetConfig + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public string ProgramName { get; set; } + public decimal DailyTarget { get; set; } + public decimal MonthlyTarget { get; set; } + public decimal YearlyTarget { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public string CreatedBy { get; set; } + public string UpdatedBy { get; set; } + } + + /// + /// Working hours configuration + /// + public class WorkingHoursConfig + { + public List WorkingDays { get; set; } + public TimeSpan WorkingHours { get; set; } + public int StartHour { get; set; } + public int EndHour { get; set; } + public List BreakIntervals { get; set; } + public bool IncludeWeekendProduction { get; set; } + public decimal WeekendOvertimeRate { get; set; } + public decimal NightShiftRate { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Alert configuration + /// + public class AlertConfiguration + { + public bool EnableAlerts { get; set; } + public List AlertTypes { get; set; } + public Dictionary AlertThresholds { get; set; } + public List NotificationChannels { get; set; } + public EmailSettings EmailSettings { get; set; } + public SMSSettings SMSSettings { get; set; } + public WebhookSettings WebhookSettings { get; set; } + public bool EnableAlertHistory { get; set; } + public int AlertRetentionDays { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Business rule configuration + /// + public class BusinessRuleConfig + { + public int RuleId { get; set; } + public string RuleName { get; set; } + public string Description { get; set; } + public string RuleExpression { get; set; } + public Dictionary Parameters { get; set; } + public bool Enabled { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public string CreatedBy { get; set; } + public string UpdatedBy { get; set; } + public List Tags { get; set; } + } + + /// + /// Statistics rule configuration + /// + public class StatisticsRuleConfig + { + public int RuleId { get; set; } + public string RuleName { get; set; } + public string Description { get; set; } + public string CalculationExpression { get; set; } + public List InputFields { get; set; } + public string OutputField { get; set; } + public CalculationMethod CalculationMethod { get; set; } + public bool Enabled { get; set; } + public int Priority { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Data retention configuration + /// + public class DataRetentionConfig + { + public int BusinessDataRetentionDays { get; set; } + public int LogDataRetentionDays { get; set; } + public int StatisticsDataRetentionDays { get; set; } + public int AlertDataRetentionDays { get; set; } + public bool AutoCleanupEnabled { get; set; } + public string CleanupSchedule { get; set; } + public bool CompressOldData { get; set; } + public bool ArchiveDataBeforeDeletion { get; set; } + public string ArchivePath { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Dashboard configuration + /// + public class DashboardConfig + { + public int RefreshInterval { get; set; } + public bool EnableRealTimeUpdates { get; set; } + public string DefaultTimeRange { get; set; } + public List AvailableTimeRanges { get; set; } + public List AvailableWidgets { get; set; } + public List DefaultWidgets { get; set; } + public bool EnableExport { get; set; } + public List ExportFormats { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Export configuration + /// + public class ExportConfig + { + public List AvailableFormats { get; set; } + public List AvailableFields { get; set; } + public int MaxRecords { get; set; } + public bool IncludeHeaders { get; set; } + public string DateFormat { get; set; } + public bool CompressFiles { get; set; } + public string DefaultFormat { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Data collection configuration + /// + public class CollectionConfig + { + public int DefaultCollectionInterval { get; set; } + public int MaxCollectionInterval { get; set; } + public int MinCollectionInterval { get; set; } + public bool EnableRetry { get; set; } + public int MaxRetries { get; set; } + public int RetryDelayMs { get; set; } + public bool EnableDataValidation { get; set; } + public bool EnableAutoRecovery { get; set; } + public List RequiredFields { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Notification configuration + /// + public class NotificationConfig + { + public bool EnableNotifications { get; set; } + public List NotificationTypes { get; set; } + public EmailSettings EmailSettings { get; set; } + public SMSSettings SMSSettings { get; set; } + public WebhookSettings WebhookSettings { get; set; } + bool PushNotificationSettings { get; set; } + public int NotificationQueueSize { get; set; } + public bool EnableNotificationHistory { get; set; } + public int NotificationRetentionDays { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + /// + /// Email settings + /// + public class EmailSettings + { + public bool EnableEmail { get; set; } + public string SmtpServer { get; set; } + public int SmtpPort { get; set; } + public string SmtpUsername { get; set; } + public string SmtpPassword { get; set; } + public bool EnableSsl { get; set; } + public List Recipients { get; set; } + public string SenderEmail { get; set; } + public string SenderName { get; set; } + public bool EnableHtmlFormat { get; set; } + } + + /// + /// SMS settings + /// + public class SMSSettings + { + public bool EnableSMS { get; set; } + public string Provider { get; set; } + public string ApiKey { get; set; } + public string ApiSecret { get; set; } + public string PhoneNumber { get; set; } + public List RecipientPhoneNumbers { get; set; } + public bool EnableTestMode { get; set; } + } + + /// + /// Webhook settings + /// + public class WebhookSettings + { + public bool EnableWebhook { get; set; } + public string WebhookUrl { get; set; } + public string HttpMethod { get; set; } + public Dictionary Headers { get; set; } + public string PayloadFormat { get; set; } + public bool EnableRetry { get; set; } + public int MaxRetries { get; set; } + public int RetryDelayMs { get; set; } + public bool EnableSignature { get; set; } + public string SignatureKey { get; set; } + } + + /// + /// Widget configuration + /// + public class WidgetConfig + { + public string WidgetType { get; set; } + public string WidgetId { get; set; } + public string Title { get; set; } + public int PositionX { get; set; } + public int PositionY { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public Dictionary Settings { get; set; } + public bool IsVisible { get; set; } + public int RefreshInterval { get; set; } + } + + /// + /// Configuration change tracking + /// + public class ConfigurationChange + { + public int ChangeId { get; set; } + public string ConfigurationType { get; set; } + public string ChangeType { get; set; } + public string ChangedBy { get; set; } + public DateTime ChangedAt { get; set; } + public string OldValue { get; set; } + public string NewValue { get; set; } + public string Reason { get; set; } + public string IPAddress { get; set; } + } + + /// + /// Calculation methods for statistics + /// + public enum CalculationMethod + { + Sum, + Average, + Minimum, + Maximum, + Count, + Median, + StandardDeviation, + Percentage, + Custom + } +} \ No newline at end of file diff --git a/Haoliang.Models/Models/System/SystemMetricsModels.cs b/Haoliang.Models/Models/System/SystemMetricsModels.cs new file mode 100644 index 0000000..d419134 --- /dev/null +++ b/Haoliang.Models/Models/System/SystemMetricsModels.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using Haoliang.Models.Common; + +namespace Haoliang.Models.Models.System +{ + /// + /// Rule execution history + /// + public class RuleExecutionHistory + { + public int ExecutionId { get; set; } + public int RuleId { get; set; } + public string RuleName { get; set; } + public string InputDataJson { get; set; } + public string Result { get; set; } + public bool Success { get; set; } + public string ErrorMessage { get; set; } + public TimeSpan ExecutionTime { get; set; } + public DateTime ExecutionTimeUtc { get; set; } + public string ExecutedBy { get; set; } + public string Context { get; set; } + } + + /// + /// Cache statistics + /// + public class CacheStats + { + public long TotalItems { get; set; } + public long HitCount { get; set; } + public long MissCount { get; set; } + public double HitRate => HitCount + MissCount > 0 ? (double)HitCount / (HitCount + MissCount) : 0; + public long MemoryUsageBytes { get; set; } + public DateTime LastCleared { get; set; } + public Dictionary ItemsByType { get; set; } + public Dictionary EvictionReasons { get; set; } + } + + /// + /// WebSocket statistics + /// + public class WebSocketStats + { + public DateTime Timestamp { get; set; } + public int ConnectedClients { get; set; } + public int TotalConnections { get; set; } + public int DisconnectedClients { get; set; } + public int ActiveStreams { get; set; } + public long MessagesSent { get; set; } + public long MessagesReceived { get; set; } + public long BytesSent { get; set; } + public long BytesReceived { get; set; } + public Dictionary ClientsByType { get; set; } + public Dictionary MessagesByType { get; set; } + } + + /// + /// System performance metrics + /// + public class PerformanceMetrics + { + public DateTime Timestamp { get; set; } + public double CpuUsagePercent { get; set; } + public double MemoryUsagePercent { get; set; } + public double DiskUsagePercent { get; set; } + public double NetworkUsageMbps { get; set; } + public int ActiveThreads { get; set; } + public int QueueLength { get; set; } + public double ResponseTimeMs { get; set; } + public double ThroughputPerSecond { get; set; } + public Dictionary CustomMetrics { get; set; } + } + + /// + /// Device performance metrics + /// + public class DevicePerformanceMetrics + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime Timestamp { get; set; } + public double CpuUsagePercent { get; set; } + public double MemoryUsagePercent { get; set; } + public double TemperatureCelsius { get; set; } + public double VibrationLevel { get; set; } + public double PowerConsumptionKW { get; set; } + public double ToolWearPercent { get; set; } + public double SpindleRpm { get; set; } + public double FeedRateMmMin { get; set; } + public double PositionAccuracyMm { get; set; } + public Dictionary CustomMetrics { get; set; } + } + + /// + /// Production quality metrics + /// + public class QualityMetrics + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + public int TotalProduced { get; set; } + public int TotalGood { get; set; } + public int TotalRejected { get; set; } + public decimal FirstPassYieldPercent { get; set; } + public decimal ReworkRatePercent { get; set; } + public decimal ScrapRatePercent { get; set; } + public decimal QualityIndex { get; set; } + public List Defects { get; set; } + public List Inspections { get; set; } + } + + /// + /// Defect analysis + /// + public class DefectAnalysis + { + public string DefectType { get; set; } + public int Count { get; set; } + public decimal Percentage { get; set; } + public decimal SeverityScore { get; set; } + public List OccurrenceTimes { get; set; } + public string RootCause { get; set; } + public string CorrectiveAction { get; set; } + public string Responsibility { get; set; } + } + + /// + /// Inspection record + /// + public class InspectionRecord + { + public int InspectionId { get; set; } + public DateTime InspectionTime { get; set; } + public string Inspector { get; set; } + public string InspectionType { get; set; } + public bool Passed { get; set; } + public decimal Score { get; set; } + public List FoundDefects { get; set; } + public string Notes { get; set; } + public string ImageUrl { get; set; } + } + + /// + /// Defect detail + /// + public class Defect + { + public string DefectType { get; set; } + public string Location { get; set; } + public decimal Severity { get; set; } + public string Description { get; set; } + public bool Critical { get; set; } + } + + /// + /// System health status + /// + public class SystemHealthStatus + { + public DateTime Timestamp { get; set; } + public SystemHealth OverallHealth { get; set; } + public Dictionary ComponentHealth { get; set; } + public List ActiveAlerts { get; set; } + public SystemUptime Uptime { get; set; } + public Dictionary HealthChecks { get; set; } + } + + /// + /// Component health status + /// + public class ComponentHealth + { + public string ComponentName { get; set; } + public HealthStatus Status { get; set; } + public double PerformanceScore { get; set; } + public double AvailabilityScore { get; set; } + public double QualityScore { get; set; } + public DateTime LastCheck { get; set; } + public string Message { get; set; } + public Dictionary Metrics { get; set; } + } + + /// + /// Health alert + /// + public class HealthAlert + { + public string AlertId { get; set; } + public string Component { get; set; } + public AlertSeverity Severity { get; set; } + public string Message { get; set; } + public DateTime OccurredAt { get; set; } + public bool Resolved { get; set; } + public DateTime? ResolvedAt { get; set; } + public string Resolution { get; set; } + } + + /// + /// System uptime information + /// + public class SystemUptime + { + public DateTime StartTime { get; set; } + public TimeSpan Uptime { get; set; } + public int RestartCount { get; set; } + public DateTime LastRestart { get; set; } + public string LastRestartReason { get; set; } + public Dictionary ComponentUptimes { get; set; } + } + + /// + /// User preferences + /// + public class UserPreferences + { + public string UserId { get; set; } + public string Language { get; set; } + public string Theme { get; set; } + public string TimeZone { get; set; } + public bool EmailNotifications { get; set; } + public bool SMSNotifications { get; set; } + public bool PushNotifications { get; set; } + public List DashboardLayout { get; set; } + public List FavoriteReports { get; set; } + public Dictionary CustomSettings { get; set; } + public DateTime LastUpdated { get; set; } + } + + /// + /// Audit log entry + /// + public class AuditLog + { + public int AuditId { get; set; } + public DateTime Timestamp { get; set; } + public string UserId { get; set; } + public string UserName { get; set; } + public string Action { get; set; } + public string EntityType { get; set; } + public int? EntityId { get; set; } + public string Description { get; set; } + public string IPAddress { get; set; } + public string UserAgent { get; set; } + public bool Success { get; set; } + public string ErrorMessage { get; set; } + public Dictionary OldValues { get; set; } + public Dictionary NewValues { get; set; } + } + + /// + /// System backup information + /// + public class BackupInfo + { + public string BackupId { get; set; } + public DateTime BackupTime { get; set; } + public string BackupType { get; set; } + public long SizeBytes { get; set; } + public string Status { get; set; } + public string Location { get; set; } + public bool IsEncrypted { get; set; } + public bool IsCompressed { get; set; } + public List IncludedComponents { get; set; } + public string VerificationHash { get; set; } + public DateTime? NextScheduledBackup { get; set; } + } + + #region Enums + + public enum SystemHealth + { + Healthy, + Warning, + Critical, + Unknown + } + + public enum HealthStatus + { + Healthy, + Degraded, + Warning, + Critical, + Unknown + } + + public enum AlertSeverity + { + Low, + Medium, + High, + Critical + } + + #endregion +} \ No newline at end of file diff --git a/Haoliang.Models/Production/AdditionalModels.cs b/Haoliang.Models/Production/AdditionalModels.cs new file mode 100644 index 0000000..989b899 --- /dev/null +++ b/Haoliang.Models/Production/AdditionalModels.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; + +namespace Haoliang.Models.Production +{ + public class ProgramProductionSummary + { + public int SummaryId { get; set; } + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public string ProgramName { get; set; } + public int Quantity { get; set; } + public DateTime ProductionDate { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + } + + public class ProductionSummary + { + public int SummaryId { get; set; } + public DateTime ProductionDate { get; set; } + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public int TotalQuantity { get; set; } + public int ProgramCount { get; set; } + public TimeSpan? TotalProductionTime { get; set; } + public decimal QualityRate { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + } + + public class ProductionStatistics + { + public DateTime Date { get; set; } + public int TotalDevices { get; set; } + public int ActiveDevices { get; set; } + public int TotalProduction { get; set; } + public decimal AverageProduction { get; set; } + public int TotalPrograms { get; set; } + public decimal QualityRate { get; set; } + public Dictionary ProductionByDevice { get; set; } + public Dictionary ProductionByProgram { get; set; } + } + + public class ProductionRecord + { + public int RecordId { get; set; } + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public string ProgramName { get; set; } + public int Quantity { get; set; } + public DateTime ProductionDate { get; set; } + public TimeSpan ProductionTime { get; set; } + public bool IsCompleted { get; set; } + public string Operator { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + } + + public class DeviceProductionSummary + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public int TotalQuantity { get; set; } + public int ProgramCount { get; set; } + public List Programs { get; set; } + } + + public class ProgramSummary + { + public string ProgramName { get; set; } + public int Quantity { get; set; } + public decimal Percentage { get; set; } + } + + public class DailyProductionSummary + { + public DateTime Date { get; set; } + public int TotalQuantity { get; set; } + public int DeviceCount { get; set; } + } + + public class WeeklyProductionSummary + { + public DateTime WeekStart { get; set; } + public DateTime WeekEnd { get; set; } + public int TotalDevices { get; set; } + public int TotalQuantity { get; set; } + public decimal AverageDailyQuantity { get; set; } + public List DailySummaries { get; set; } + } + + public class MonthlyProductionSummary + { + public int Year { get; set; } + public int Month { get; set; } + public int TotalDevices { get; set; } + public int TotalQuantity { get; set; } + public decimal AverageDailyQuantity { get; set; } + public List WeeklySummaries { get; set; } + } + + public class ProductionYield + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public DateTime Date { get; set; } + public int TotalProduced { get; set; } + public int GoodPieces { get; set; } + public int DefectivePieces { get; set; } + public decimal QualityPercentage { get; set; } + public string Shift { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Models/System/AdditionalModels.cs b/Haoliang.Models/System/AdditionalModels.cs new file mode 100644 index 0000000..9e6493d --- /dev/null +++ b/Haoliang.Models/System/AdditionalModels.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; + +namespace Haoliang.Models.System +{ + public class SystemConfig + { + public int Id { get; set; } + public string ConfigKey { get; set; } + public string ConfigValue { get; set; } + public string Description { get; set; } + public string Category { get; set; } + public bool IsActive { get; set; } + public bool IsDefault { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + public class ScheduledTask + { + public string TaskId { get; set; } + public string TaskName { get; set; } + public string CronExpression { get; set; } + public string Description { get; set; } + public TaskStatus TaskStatus { get; set; } + public bool IsActive { get; set; } + public DateTime? LastRunAt { get; set; } + public DateTime? NextRunTime { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? CompletedAt { get; set; } + public string ErrorMessage { get; set; } + } + + public class TaskExecutionResult + { + public int ExecutionId { get; set; } + public string TaskId { get; set; } + public TaskStatus Status { get; set; } + public DateTime ExecutionTime { get; set; } + public TimeSpan? ExecutionDurationMs { get; set; } + public string ErrorMessage { get; set; } + public Dictionary ResultData { get; set; } + } + + public class TaskExecutionSummary + { + public DateTime Date { get; set; } + public int TotalExecutions { get; set; } + public int SuccessfulExecutions { get; set; } + public int FailedExecutions { get; set; } + public int RunningExecutions { get; set; } + public Dictionary ExecutionDetails { get; set; } + } + + public class TaskExecutionDetail + { + public string TaskName { get; set; } + public int TotalExecutions { get; set; } + public int SuccessfulExecutions { get; set; } + public int FailedExecutions { get; set; } + public double AverageExecutionTime { get; set; } + } + + public class LogStatistics + { + public DateTime Date { get; set; } + public int TotalLogs { get; set; } + public int ErrorLogs { get; set; } + public int WarningLogs { get; set; } + public int InfoLogs { get; set; } + public int DebugLogs { get; set; } + public Dictionary LogSources { get; set; } + } + + public enum TaskStatus + { + Pending = 0, + Running = 1, + Completed = 2, + Failed = 3, + Disabled = 4 + } + + public enum AlarmPriority + { + Low = 1, + Medium = 2, + High = 3, + Critical = 4 + } + + public class DeviceStatistics + { + public int DeviceId { get; set; } + public string DeviceName { get; set; } + public int TotalCollections { get; set; } + public int SuccessfulCollections { get; set; } + public int FailedCollections { get; set; } + public double SuccessRate { get; set; } + public TimeSpan AverageResponseTime { get; set; } + public DateTime LastCollectionTime { get; set; } + } + + public class CollectionTaskStatistics + { + public DateTime Date { get; set; } + public int TotalTasks { get; set; } + public int PendingTasks { get; set; } + public int RunningTasks { get; set; } + public int CompletedTasks { get; set; } + public int FailedTasks { get; set; } + public Dictionary DeviceTasks { get; set; } + } + + public class CollectionHealth + { + public DateTime CheckTime { get; set; } + public int TotalDevices { get; set; } + public int OnlineDevices { get; set; } + public int ActiveCollectionTasks { get; set; } + public int FailedTasks { get; set; } + public decimal SuccessRate { get; set; } + public TimeSpan AverageResponseTime { get; set; } + public long TotalCollectedData { get; set; } + public DateTime LastSuccessfulCollection { get; set; } + public DateTime LastFailedCollection { get; set; } + } + + public class CollectionStatistics + { + public DateTime Date { get; set; } + public int TotalAttempts { get; set; } + public int SuccessCount { get; set; } + public int FailedCount { get; set; } + public decimal SuccessRate { get; set; } + public int DeviceCount { get; set; } + public int OnlineDeviceCount { get; set; } + public long TotalDataSize { get; set; } + public TimeSpan? AverageResponseTime { get; set; } + } + + public class AverageResponseTime + { + public int DeviceId { get; set; } + public DateTime Date { get; set; } + public TimeSpan AverageTime { get; set; } + public int SampleCount { get; set; } + } + + public class CollectionLogStatistics + { + public DateTime Date { get; set; } + public int TotalLogs { get; set; } + public int ErrorLogs { get; set; } + public int WarningLogs { get; set; } + public int InfoLogs { get; set; } + public int DebugLogs { get; set; } + public Dictionary DeviceLogs { get; set; } + public Dictionary LogCategories { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Models/System/Enums.cs b/Haoliang.Models/System/Enums.cs new file mode 100644 index 0000000..cf456ff --- /dev/null +++ b/Haoliang.Models/System/Enums.cs @@ -0,0 +1,121 @@ +namespace Haoliang.Models.System +{ + public enum LogLevel + { + Trace = 0, + Debug = 1, + Information = 2, + Warning = 3, + Error = 4, + Critical = 5, + None = 6 + } + + public enum AlarmType + { + DeviceOffline = 1, + DeviceError = 2, + ProductionError = 3, + SystemError = 4, + NetworkError = 5, + Maintenance = 6 + } + + public enum AlarmStatus + { + Active = 1, + Acknowledged = 2, + Resolved = 3, + Suppressed = 4 + } + + public enum AlarmSeverity + { + Low = 1, + Medium = 2, + High = 3, + Critical = 4 + } + + public enum NotificationType + { + Email = 1, + SMS = 2, + WeChat = 3, + System = 4 + } + + public enum NotificationStatus + { + Pending = 1, + Sent = 2, + Failed = 3, + Delivered = 4 + } + + public class Alarm + { + public int AlarmId { get; set; } + public int DeviceId { get; set; } + public string DeviceCode { get; set; } + public AlarmType AlarmType { get; set; } + public AlarmSeverity Severity { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public AlarmStatus AlarmStatus { get; set; } + public DateTime CreateTime { get; set; } + public DateTime? AcknowledgedTime { get; set; } + public DateTime? ResolvedTime { get; set; } + public string AcknowledgeNote { get; set; } + public string ResolutionNote { get; set; } + public bool IsActive => AlarmStatus == AlarmStatus.Active; + } + + public class AlarmRule + { + public int RuleId { get; set; } + public string RuleName { get; set; } + public int DeviceId { get; set; } + public AlarmType AlarmType { get; set; } + public string Condition { get; set; } + public string Threshold { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + public class AlarmNotification + { + public int NotificationId { get; set; } + public int AlarmId { get; set; } + public NotificationType NotificationType { get; set; } + public string Recipient { get; set; } + public string Message { get; set; } + public NotificationStatus Status { get; set; } + public DateTime SendTime { get; set; } + public string ErrorMessage { get; set; } + public DateTime? RetryTime { get; set; } + } + + public class AlarmStatistics + { + public int TotalAlarms { get; set; } + public int ActiveAlarms { get; set; } + public int CriticalAlarms { get; set; } + public int ResolvedAlarms { get; set; } + public Dictionary AlarmsByType { get; set; } + public Dictionary AlarmsBySeverity { get; set; } + } + + public class LogEntry + { + public int LogId { get; set; } + public LogLevel LogLevel { get; set; } + public string Category { get; set; } + public string Message { get; set; } + public string ExceptionMessage { get; set; } + public string StackTrace { get; set; } + public Dictionary Properties { get; set; } + public DateTime Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Models/System/SystemTypes.cs b/Haoliang.Models/System/SystemTypes.cs new file mode 100644 index 0000000..81f5ee4 --- /dev/null +++ b/Haoliang.Models/System/SystemTypes.cs @@ -0,0 +1,74 @@ +namespace Haoliang.Models.System +{ + public class SystemConfig + { + public int ConfigId { get; set; } + public string ConfigKey { get; set; } + public string ConfigValue { get; set; } + public string Category { get; set; } + public string Description { get; set; } + public DateTime CreateTime { get; set; } + public DateTime LastUpdated { get; set; } + public bool IsSystem { get; set; } + } + + public class ScheduledTask + { + public string TaskId { get; set; } + public string TaskName { get; set; } + public string CronExpression { get; set; } + public TaskStatus TaskStatus { get; set; } + public bool IsActive { get; set; } + public string Description { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? LastRunAt { get; set; } + public DateTime? CompletedAt { get; set; } + public string ErrorMessage { get; set; } + } + + public class TaskExecutionResult + { + public string TaskId { get; set; } + public bool IsSuccess { get; set; } + public string ErrorMessage { get; set; } + public object Result { get; set; } + public DateTime ExecutionTime { get; set; } + public TimeSpan Duration { get; set; } + } + + public enum TaskStatus + { + Pending = 1, + Running = 2, + Completed = 3, + Failed = 4, + Cancelled = 5 + } + + public class SystemMessage + { + public int MessageId { get; set; } + public string MessageType { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Timestamp { get; set; } + public bool IsRead { get; set; } + Dictionary Metadata { get; set; } + } + + public class SystemHealth + { + public string Status { get; set; } + public DateTime Timestamp { get; set; } + public List Checks { get; set; } + public Dictionary Metrics { get; set; } + } + + public class HealthCheck + { + public string Name { get; set; } + public string Status { get; set; } + public string Message { get; set; } + public Exception Error { get; set; } + } +} \ No newline at end of file diff --git a/Haoliang.Models/User/User.cs b/Haoliang.Models/User/User.cs index 83fe4a4..d015fd5 100644 --- a/Haoliang.Models/User/User.cs +++ b/Haoliang.Models/User/User.cs @@ -64,17 +64,4 @@ namespace Haoliang.Models.User public User User { get; set; } public string Message { get; set; } } - - public class UserViewModel - { - public int Id { get; set; } - public string Username { get; set; } - public string RealName { get; set; } - public string Email { get; set; } - public string Phone { get; set; } - public string RoleName { get; set; } - public bool IsActive { get; set; } - public DateTime? LastLoginTime { get; set; } - public DateTime CreatedAt { get; set; } - } } \ No newline at end of file diff --git a/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.dll b/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.dll index e2a506b..f6f81a4 100644 Binary files a/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.dll and b/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.dll differ diff --git a/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.pdb b/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.pdb index ef0abc9..a4f1f2a 100644 Binary files a/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.pdb and b/Haoliang.Models/bin/Debug/net6.0/Haoliang.Models.pdb differ diff --git a/Haoliang.Models/bin/Debug/net6.0/ref/Haoliang.Models.dll b/Haoliang.Models/bin/Debug/net6.0/ref/Haoliang.Models.dll index 6bedd5f..412dca7 100644 Binary files a/Haoliang.Models/bin/Debug/net6.0/ref/Haoliang.Models.dll and b/Haoliang.Models/bin/Debug/net6.0/ref/Haoliang.Models.dll differ diff --git a/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.csproj.CoreCompileInputs.cache b/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.csproj.CoreCompileInputs.cache index dd592c4..7cb8c04 100644 --- a/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.csproj.CoreCompileInputs.cache +++ b/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -e7163c00c0336524fd5e676f54b24312e59335ff +e646755fc76efe336c702b67be8a443e31ae6973 diff --git a/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.dll b/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.dll index e2a506b..f6f81a4 100644 Binary files a/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.dll and b/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.dll differ diff --git a/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.pdb b/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.pdb index ef0abc9..a4f1f2a 100644 Binary files a/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.pdb and b/Haoliang.Models/obj/Debug/net6.0/Haoliang.Models.pdb differ diff --git a/Haoliang.Models/obj/Debug/net6.0/ref/Haoliang.Models.dll b/Haoliang.Models/obj/Debug/net6.0/ref/Haoliang.Models.dll index 6bedd5f..412dca7 100644 Binary files a/Haoliang.Models/obj/Debug/net6.0/ref/Haoliang.Models.dll and b/Haoliang.Models/obj/Debug/net6.0/ref/Haoliang.Models.dll differ diff --git a/Haoliang.Tests/CacheServiceTests.cs b/Haoliang.Tests/CacheServiceTests.cs new file mode 100644 index 0000000..8cc8299 --- /dev/null +++ b/Haoliang.Tests/CacheServiceTests.cs @@ -0,0 +1,460 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Moq; +using Microsoft.Extensions.Caching.Memory; +using Haoliang.Core.Services; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.Production; + +namespace Haoliang.Tests.Services +{ + public class CacheServiceTests + { + private readonly Mock _mockMemoryCache; + private readonly Mock _mockProductionRepository; + private readonly Mock _mockDeviceRepository; + private readonly Mock _mockCollectionRepository; + private readonly CacheService _cacheService; + + public CacheServiceTests() + { + _mockMemoryCache = new Mock(); + _mockProductionRepository = new Mock(); + _mockDeviceRepository = new Mock(); + _mockCollectionRepository = new Mock(); + + _cacheService = new CacheService(_mockMemoryCache.Object); + } + + [Fact] + public async Task GetOrSetAsync_ValueNotInCache_ExecutesFactoryAndCachesResult() + { + // Arrange + var key = "test-key"; + var factoryResult = "test-value"; + + var factoryMock = new Mock>>(); + factoryMock.Setup(f => f()).ReturnsAsync(factoryResult); + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Returns(false); + + // Setup cache set + _mockMemoryCache.Setup(cache => cache.Set( + key, + factoryResult, + It.IsAny())) + .Callback((k, v, o) => { }); + + // Act + var result = await _cacheService.GetOrSetAsync(key, factoryMock.Object); + + // Assert + Assert.Equal(factoryResult, result); + factoryMock.Verify(f => f(), Times.Once); + _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); + _mockMemoryCache.Verify(cache => cache.Set( + key, + factoryResult, + It.IsAny()), Times.Once); + } + + [Fact] + public async Task GetOrSetAsync_ValueInCache_ReturnsCachedValue() + { + // Arrange + var key = "test-key"; + var cachedValue = "cached-value"; + + var factoryMock = new Mock>>(); + factoryMock.Setup(f => f()).ReturnsAsync("new-value"); + + // Setup cache hit + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Callback((k, out string v) => v = cachedValue) + .Returns(true); + + // Act + var result = await _cacheService.GetOrSetAsync(key, factoryMock.Object); + + // Assert + Assert.Equal(cachedValue, result); + factoryMock.Verify(f => f(), Times.Never); + _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); + _mockMemoryCache.Verify(cache => cache.Set( + key, + It.IsAny(), + It.IsAny()), Times.Never); + } + + [Fact] + public void Get_ValueExists_ReturnsCachedValue() + { + // Arrange + var key = "test-key"; + var cachedValue = "cached-value"; + + // Setup cache hit + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Callback((k, out string v) => v = cachedValue) + .Returns(true); + + // Act + var result = _cacheService.Get(key); + + // Assert + Assert.Equal(cachedValue, result); + _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); + } + + [Fact] + public void Get_ValueNotExists_ReturnsDefault() + { + // Arrange + var key = "test-key"; + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Returns(false); + + // Act + var result = _cacheService.Get(key); + + // Assert + Assert.Null(result); + _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); + } + + [Fact] + public void Set_ValidValue_CachesValue() + { + // Arrange + var key = "test-key"; + var value = "test-value"; + + var mockEntryOptions = new Mock(); + _mockMemoryCache.Setup(cache => cache.Set(key, value, mockEntryOptions.Object)) + .Verifiable(); + + // Act + _cacheService.Set(key, value, mockEntryOptions.Object); + + // Assert + _mockMemoryCache.Verify(cache => cache.Set(key, value, mockEntryOptions.Object), Times.Once); + } + + [Fact] + public void Set_NullValue_DoesNotCache() + { + // Arrange + var key = "test-key"; + + // Act + _cacheService.Set(key, null); + + // Assert + _mockMemoryCache.Verify(cache => cache.Set( + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); + } + + [Fact] + public void Remove_KeyExists_RemovesFromCache() + { + // Arrange + var key = "test-key"; + + // Setup cache hit for removal check + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Callback((k, out object v) => v = "some-value") + .Returns(true); + + _mockMemoryCache.Setup(cache => cache.Remove(key)) + .Verifiable(); + + // Act + var result = _cacheService.Remove(key); + + // Assert + Assert.True(result); + _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Once); + } + + [Fact] + public void Remove_KeyNotExists_ReturnsFalse() + { + // Arrange + var key = "test-key"; + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Returns(false); + + // Act + var result = _cacheService.Remove(key); + + // Assert + Assert.False(result); + _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never); + } + + [Fact] + public void Exists_KeyExists_ReturnsTrue() + { + // Arrange + var key = "test-key"; + + // Setup cache hit + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Returns(true); + + // Act + var result = _cacheService.Exists(key); + + // Assert + Assert.True(result); + _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); + } + + [Fact] + public void Exists_KeyNotExists_ReturnsFalse() + { + // Arrange + var key = "test-key"; + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Returns(false); + + // Act + var result = _cacheService.Exists(key); + + // Assert + Assert.False(result); + _mockMemoryCache.Verify(cache => cache.TryGetValue(key, out It.Ref.IsAny), Times.Once); + } + + [Fact] + public void Clear_ClearsAllCache() + { + // Act + _cacheService.Clear(); + + // Assert + _mockMemoryCache.Verify(cache => cache.Compact(1.0), Times.Once); + } + + [Fact] + public void GetStatistics_ReturnsCacheStatistics() + { + // Arrange + _mockMemoryCache.Setup(cache => cache.Count) + .Returns(5); + + // Act + var result = _cacheService.GetStatistics(); + + // Assert + Assert.NotNull(result); + Assert.True(result.TotalItems >= 0); + Assert.True(result.HitRate >= 0 && result.HitRate <= 1); + Assert.True(result.MemoryUsageBytes >= 0); + Assert.NotNull(result.ItemsByType); + } + + [Fact] + public void GetKeys_MatchingPattern_ReturnsKeys() + { + // Arrange + var pattern = "device:*"; + var expectedKeys = new List { "device:1", "device:2", "device:3" }; + + // Mock cache keys + var mockKeys = new List { "device:1", "template:1", "device:2", "config:1", "device:3" }; + _mockMemoryCache.Setup(cache => cache.Keys) + .Returns(mockKeys.Cast().ToList()); + + // Act + var result = _cacheService.GetKeys(pattern).ToList(); + + // Assert + Assert.Equal(3, result.Count); + Assert.Contains("device:1", result); + Assert.Contains("device:2", result); + Assert.Contains("device:3", result); + } + + [Fact] + public void GetKeys_NoMatches_ReturnsEmptyList() + { + // Arrange + var pattern = "nonexistent:*"; + + // Mock cache keys + var mockKeys = new List { "device:1", "template:1", "config:1" }; + _mockMemoryCache.Setup(cache => cache.Keys) + .Returns(mockKeys.Cast().ToList()); + + // Act + var result = _cacheService.GetKeys(pattern).ToList(); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void Refresh_ExistingKey_RefreshesCache() + { + // Arrange + var key = "test-key"; + var value = "test-value"; + var mockOptions = new MemoryCacheEntryOptions(); + + // Setup cache hit + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Callback((k, out object v) => v = value) + .Returns(true); + + _mockMemoryCache.Setup(cache => cache.Remove(key)) + .Verifiable(); + + _mockMemoryCache.Setup(cache => cache.Set(key, value, mockOptions)) + .Verifiable(); + + // Act + var result = _cacheService.Refresh(key); + + // Assert + Assert.True(result); + _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Once); + _mockMemoryCache.Verify(cache => cache.Set(key, value, mockOptions), Times.Once); + } + + [Fact] + public void Refresh_NonExistingKey_ReturnsFalse() + { + // Arrange + var key = "nonexistent-key"; + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(key, out It.Ref.IsAny)) + .Returns(false); + + // Act + var result = _cacheService.Refresh(key); + + // Assert + Assert.False(result); + _mockMemoryCache.Verify(cache => cache.Remove(key), Times.Never); + _mockMemoryCache.Verify(cache => cache.Set( + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); + } + + [Fact] + public async Task GetOrSetDeviceAsync_ValidDevice_ReturnsDevice() + { + // Arrange + var deviceId = 1; + var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; + + var factoryMock = new Mock>>(); + factoryMock.Setup(f => f()).ReturnsAsync(device); + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) + .Returns(false); + + // Setup cache set + _mockMemoryCache.Setup(cache => cache.Set( + It.IsAny(), + device, + It.IsAny())) + .Callback((k, v, o) => { }); + + // Act + var result = await _cacheService.GetOrSetDeviceAsync(deviceId, factoryMock.Object); + + // Assert + Assert.Equal(device, result); + factoryMock.Verify(f => f(), Times.Once); + } + + [Fact] + public void InvalidateDeviceCache_ValidDevice_RemovesRelatedKeys() + { + // Arrange + var deviceId = 1; + + // Setup cache hits for removal + _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) + .Callback((k, out object v) => v = "some-value") + .Returns(true); + + _mockMemoryCache.Setup(cache => cache.Remove(It.IsAny())) + .Verifiable(); + + // Act + _cacheService.InvalidateDeviceCache(deviceId, "additional:key"); + + // Assert + _mockMemoryCache.Verify(cache => cache.Remove("device:1"), Times.Once); + _mockMemoryCache.Verify(cache => cache.Remove("device:status:1"), Times.Once); + _mockMemoryCache.Verify(cache => cache.Remove(It.IsAny()), Times.AtLeast(3)); + } + + [Fact] + public async Task GetOrSetSystemConfigurationAsync_ValidFactory_ReturnsConfiguration() + { + // Arrange + var config = new SystemConfiguration { DailyProductionTarget = 100 }; + + var factoryMock = new Mock>>(); + factoryMock.Setup(f => f()).ReturnsAsync(config); + + // Setup cache miss + _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) + .Returns(false); + + // Setup cache set + _mockMemoryCache.Setup(cache => cache.Set( + It.IsAny(), + config, + It.IsAny())) + .Callback((k, v, o) => { }); + + // Act + var result = await _cacheService.GetOrSetSystemConfigurationAsync(factoryMock.Object); + + // Assert + Assert.Equal(config, result); + factoryMock.Verify(f => f(), Times.Once); + } + + [Fact] + public void InvalidateSystemConfigCache_RemovesSystemConfigKeys() + { + // Arrange + // Setup cache hits for removal + _mockMemoryCache.Setup(cache => cache.TryGetValue(It.IsAny(), out It.Ref.IsAny)) + .Callback((k, out object v) => v = "some-value") + .Returns(true); + + _mockMemoryCache.Setup(cache => cache.Remove(It.IsAny())) + .Verifiable(); + + // Act + _cacheService.InvalidateSystemConfigCache(); + + // Assert + _mockMemoryCache.Verify(cache => cache.Remove("config:system"), Times.Once); + _mockMemoryCache.Verify(cache => cache.Remove("config:alerts"), Times.Once); + } + } +} \ No newline at end of file diff --git a/Haoliang.Tests/ControllerTests.cs b/Haoliang.Tests/ControllerTests.cs new file mode 100644 index 0000000..8dfba79 --- /dev/null +++ b/Haoliang.Tests/ControllerTests.cs @@ -0,0 +1,765 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Moq; +using Microsoft.AspNetCore.Mvc; +using Haoliang.Api.Controllers; +using Haoliang.Core.Services; +using Haoliang.Models.Models.System; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Common; + +namespace Haoliang.Tests.Controllers +{ + public class StatisticsControllerTests + { + private readonly Mock _mockStatisticsService; + private readonly StatisticsController _controller; + + public StatisticsControllerTests() + { + _mockStatisticsService = new Mock(); + _controller = new StatisticsController(_mockStatisticsService.Object); + } + + [Fact] + public async Task GetProductionTrendsAsync_ValidRequest_ReturnsOk() + { + // Arrange + var deviceId = 1; + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + + var trendAnalysis = new ProductionTrendAnalysis + { + DeviceId = deviceId, + DeviceName = "Test Device", + PeriodStart = startDate, + PeriodEnd = endDate, + TotalProduction = 1000, + AverageDailyProduction = 142.86m, + TrendCoefficient = 0.5m, + TrendDirection = ProductionTrendDirection.Increasing, + DailyData = new List() + }; + + _mockStatisticsService.Setup(service => service.CalculateProductionTrendsAsync(deviceId, startDate, endDate)) + .ReturnsAsync(trendAnalysis); + + // Act + var result = await _controller.GetProductionTrends(deviceId, startDate, endDate); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(trendAnalysis, response.Data); + } + + [Fact] + public async Task GetProductionTrendsAsync_InvalidDevice_ReturnsNotFound() + { + // Arrange + var deviceId = 999; + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + + _mockStatisticsService.Setup(service => service.CalculateProductionTrendsAsync(deviceId, startDate, endDate)) + .ThrowsAsync(new KeyNotFoundException("Device not found")); + + // Act + var result = await _controller.GetProductionTrends(deviceId, startDate, endDate); + + // Assert + var notFoundResult = Assert.IsType(result); + var response = Assert.IsType>(notFoundResult.Value); + Assert.False(response.Success); + Assert.Contains("Device not found", response.Message); + } + + [Fact] + public async Task GetProductionReportAsync_ValidFilter_ReturnsOk() + { + // Arrange + var filter = new ReportFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + ReportType = ReportType.Daily + }; + + var productionReport = new ProductionReport + { + ReportDate = DateTime.Now, + ReportType = ReportType.Daily, + SummaryItems = new List(), + Metadata = ReportMetadata.GeneratedAt + }; + + _mockStatisticsService.Setup(service => service.GenerateProductionReportAsync(filter)) + .ReturnsAsync(productionReport); + + // Act + var result = await _controller.GetProductionReport(filter); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(productionReport, response.Data); + } + + [Fact] + public async Task GetDashboardSummaryAsync_ValidFilter_ReturnsOk() + { + // Arrange + var filter = new DashboardFilter + { + DeviceIds = new List { 1 }, + Date = DateTime.Today, + IncludeAlerts = true + }; + + var dashboardSummary = new DashboardSummary + { + GeneratedAt = DateTime.Now, + TotalDevices = 10, + ActiveDevices = 8, + OfflineDevices = 2, + TotalProductionToday = 1000, + TotalProductionThisWeek = 7000, + TotalProductionThisMonth = 30000, + OverallEfficiency = 85.5m, + QualityRate = 98.2m, + DeviceSummaries = new List(), + ActiveAlerts = new List() + }; + + _mockStatisticsService.Setup(service => service.GetDashboardSummaryAsync(filter)) + .ReturnsAsync(dashboardSummary); + + // Act + var result = await _controller.GetDashboardSummary(filter); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(dashboardSummary, response.Data); + } + + [Fact] + public async Task GetEfficiencyMetricsAsync_ValidFilter_ReturnsOk() + { + // Arrange + var filter = new EfficiencyFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + Metrics = EfficiencyMetric.Oee + }; + + var efficiencyMetrics = new EfficiencyMetrics + { + DeviceId = 1, + DeviceName = "Test Device", + PeriodStart = DateTime.Now.AddDays(-7), + PeriodEnd = DateTime.Now, + Availability = 95m, + Performance = 90m, + Quality = 98m, + Oee = 83.79m, + Utilization = EquipmentUtilization.Optimal, + HourlyData = new List() + }; + + _mockStatisticsService.Setup(service => service.CalculateEfficiencyMetricsAsync(filter)) + .ReturnsAsync(efficiencyMetrics); + + // Act + var result = await _controller.GetEfficiencyMetrics(filter); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(efficiencyMetrics, response.Data); + } + + [Fact] + public async Task GetOeeMetricsAsync_ValidDeviceAndDate_ReturnsOk() + { + // Arrange + var deviceId = 1; + var date = DateTime.Today; + + var oeeMetrics = new OeeMetrics + { + DeviceId = deviceId, + DeviceName = "Test Device", + Date = date, + Availability = 95m, + Performance = 90m, + Quality = 98m, + Oee = 83.79m, + PlannedProductionTime = TimeSpan.FromHours(8), + ActualProductionTime = TimeSpan.FromHours(7.6), + Downtime = TimeSpan.FromMinutes(24), + IdealCycleTime = 2, + TotalCycleTime = 500, + TotalPieces = 250, + GoodPieces = 245 + }; + + _mockStatisticsService.Setup(service => service.CalculateOeeAsync(deviceId, date)) + .ReturnsAsync(oeeMetrics); + + // Act + var result = await _controller.GetOeeMetrics(deviceId, date); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(oeeMetrics, response.Data); + } + + [Fact] + public async Task GetProductionForecastAsync_ValidFilter_ReturnsOk() + { + // Arrange + var filter = new ForecastFilter + { + DeviceId = 1, + DaysToForecast = 7, + Model = ForecastModel.Linear, + HistoricalDataStart = DateTime.Now.AddDays(-30), + HistoricalDataEnd = DateTime.Now + }; + + var productionForecast = new ProductionForecast + { + DeviceId = 1, + DeviceName = "Test Device", + ForecastStartDate = DateTime.Today, + ForecastEndDate = DateTime.Today.AddDays(6), + DailyForecasts = new List(), + ForecastAccuracy = 85.5m, + ModelUsed = ForecastModel.Linear + }; + + _mockStatisticsService.Setup(service => service.GenerateProductionForecastAsync(filter)) + .ReturnsAsync(productionForecast); + + // Act + var result = await _controller.GetProductionForecast(filter); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(productionForecast, response.Data); + } + + [Fact] + public async Task DetectProductionAnomaliesAsync_ValidFilter_ReturnsOk() + { + // Arrange + var filter = new AnomalyFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + MinSeverity = AnomalySeverity.Medium + }; + + var anomalyAnalysis = new AnomalyAnalysis + { + DeviceIds = new List { 1 }, + DeviceNames = new List { "Test Device" }, + AnalysisStartDate = DateTime.Now.AddDays(-7), + AnalysisEndDate = DateTime.Now, + Anomalies = new List(), + OverallSeverity = AnomalySeverity.Low + }; + + _mockStatisticsService.Setup(service => service.DetectProductionAnomaliesAsync(filter)) + .ReturnsAsync(anomalyAnalysis); + + // Act + var result = await _controller.DetectProductionAnomalies(filter); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(anomalyAnalysis, response.Data); + } + + [Fact] + public async Task GetHistoricalProductionDataAsync_ValidRequest_ReturnsOk() + { + // Arrange + var deviceId = 1; + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + var groupBy = GroupBy.Date; + + var historicalData = new HistoricalProductionData + { + DeviceId = deviceId, + PeriodStart = startDate, + PeriodEnd = endDate, + GroupBy = groupBy, + DataPoints = new List() + }; + + _mockStatisticsService.Setup(service => service.GenerateProductionReportAsync(It.IsAny())) + .ReturnsAsync(new ProductionReport { SummaryItems = new List() }); + + // Mock the conversion in the controller + // This would normally be done by the controller logic + + // Act + var result = await _controller.GetHistoricalProductionData(deviceId, startDate, endDate, groupBy); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(deviceId, response.Data.DeviceId); + } + + [Fact] + public async Task GetHistoricalProductionDataAsync_InvalidDevice_ReturnsBadRequest() + { + // Arrange + var deviceId = 0; // Invalid device ID + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + + // Act + var result = await _controller.GetHistoricalProductionData(deviceId, startDate, endDate); + + // Assert + var badRequestResult = Assert.IsType(result); + var response = Assert.IsType>(badRequestResult.Value); + Assert.False(response.Success); + Assert.Contains("Invalid device ID", response.Message); + } + + [Fact] + public async Task GetEfficiencyTrendsAsync_ValidRequest_ReturnsOk() + { + // Arrange + var deviceId = 1; + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + var metric = EfficiencyMetric.Oee; + + var efficiencyTrendData = new EfficiencyTrendData + { + DeviceId = deviceId, + Metric = metric, + PeriodStart = startDate, + PeriodEnd = endDate, + DataPoints = new List() + }; + + _mockStatisticsService.Setup(service => service.CalculateEfficiencyMetricsAsync(It.IsAny())) + .ReturnsAsync(new EfficiencyMetrics { HourlyData = new List() }); + + // Act + var result = await _controller.GetEfficiencyTrends(deviceId, startDate, endDate, metric); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(deviceId, response.Data.DeviceId); + } + + [Fact] + public async Task GetMultiDeviceSummaryAsync_ValidDevices_ReturnsOk() + { + // Arrange + var deviceIds = new List { 1, 2, 3 }; + + _mockStatisticsService.Setup(service => service.GetDashboardSummaryAsync(It.IsAny())) + .ReturnsAsync(new DashboardSummary + { + TotalDevices = 3, + ActiveDevices = 2, + OfflineDevices = 1, + TotalProductionToday = 1000, + TotalProductionThisWeek = 7000, + TotalProductionThisMonth = 30000, + OverallEfficiency = 85.5m, + QualityRate = 98.2m, + DeviceSummaries = new List() + }); + + // Act + var result = await _controller.GetMultiDeviceSummary(deviceIds); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(3, response.Data.DeviceCount); + Assert.Equal(2, response.Data.ActiveDeviceCount); + Assert.Equal(1, response.Data.OfflineDeviceCount); + } + } + + public class ConfigControllerTests + { + private readonly Mock _mockSystemService; + private readonly Mock _mockTemplateService; + private readonly Mock _mockRulesService; + private readonly Mock _mockStatisticsService; + private readonly ConfigController _controller; + + public ConfigControllerTests() + { + _mockSystemService = new Mock(); + _mockTemplateService = new Mock(); + _mockRulesService = new Mock(); + _mockStatisticsService = new Mock(); + _controller = new ConfigController( + _mockSystemService.Object, + _mockTemplateService.Object, + _mockRulesService.Object, + _mockStatisticsService.Object + ); + } + + [Fact] + public async Task GetSystemConfigurationAsync_ValidRequest_ReturnsOk() + { + // Arrange + var systemConfig = new SystemConfiguration + { + DailyProductionTarget = 100, + EnableProduction = true, + EnableAlerts = true, + AlertThresholds = new Dictionary + { + { "production_drop", 30 }, + { "quality_rate", 90 } + } + }; + + _mockSystemService.Setup(service => service.GetSystemConfigurationAsync()) + .ReturnsAsync(systemConfig); + + // Act + var result = await _controller.GetSystemConfiguration(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(systemConfig, response.Data); + } + + [Fact] + public async Task UpdateSystemConfigurationAsync_ValidConfig_ReturnsOk() + { + // Arrange + var config = new SystemConfiguration + { + DailyProductionTarget = 150, + EnableProduction = true, + EnableAlerts = true + }; + + _mockSystemService.Setup(service => service.UpdateSystemConfigurationAsync(config)) + .ReturnsAsync(true); + + // Act + var result = await _controller.UpdateSystemConfiguration(config); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.True(response.Data); + } + + [Fact] + public async Task GetProductionTargetsAsync_ValidRequest_ReturnsOk() + { + // Arrange + var targets = new List + { + new ProductionTargetConfig { DeviceId = 1, ProgramName = "Program1", DailyTarget = 100 }, + new ProductionTargetConfig { DeviceId = 2, ProgramName = "Program2", DailyTarget = 150 } + }; + + _mockSystemService.Setup(service => service.GetProductionTargetsAsync()) + .ReturnsAsync(targets); + + // Act + var result = await _controller.GetProductionTargets(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(2, response.Data.Count); + } + + [Fact] + public async Task GetWorkingHoursConfigAsync_ValidRequest_ReturnsOk() + { + // Arrange + var workingHoursConfig = new WorkingHoursConfig + { + WorkingDays = new List { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }, + WorkingHours = TimeSpan.FromHours(8), + StartHour = 9, + EndHour = 17, + BreakIntervals = new List + { + TimeSpan.FromHours(12) // Lunch break + } + }; + + _mockSystemService.Setup(service => service.GetWorkingHoursConfigAsync()) + .ReturnsAsync(workingHoursConfig); + + // Act + var result = await _controller.GetWorkingHoursConfig(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(5, response.Data.WorkingDays.Count); + } + + [Fact] + public async Task GetAlertConfigurationAsync_ValidRequest_ReturnsOk() + { + // Arrange + var alertConfig = new AlertConfiguration + { + EnableAlerts = true, + AlertTypes = new List { "production_drop", "quality_rate", "device_error" }, + AlertThresholds = new Dictionary + { + { "production_drop", 30 }, + { "quality_rate", 90 }, + { "device_error", 5 } + }, + NotificationChannels = new List { "email", "sms", "webhook" } + }; + + _mockSystemService.Setup(service => service.GetAlertConfigurationAsync()) + .ReturnsAsync(alertConfig); + + // Act + var result = await _controller.GetAlertConfiguration(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(3, response.Data.AlertTypes.Count); + } + + [Fact] + public async Task GetBusinessRulesAsync_ValidRequest_ReturnsOk() + { + // Arrange + var rules = new List + { + new BusinessRuleConfig { RuleId = 1, RuleName = "Production Target", Enabled = true }, + new BusinessRuleConfig { RuleId = 2, RuleName = "Quality Control", Enabled = false } + }; + + _mockRulesService.Setup(service => service.GetAllRulesAsync()) + .ReturnsAsync(rules); + + // Act + var result = await _controller.GetBusinessRules(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(2, response.Data.Count); + } + + [Fact] + public async Task CreateOrUpdateBusinessRuleAsync_ValidRule_ReturnsOk() + { + // Arrange + var rule = new BusinessRuleConfig + { + RuleId = 1, + RuleName = "Production Target", + Enabled = true, + RuleExpression = "production > target * 0.9" + }; + + _mockRulesService.Setup(service => service.CreateOrUpdateRuleAsync(rule)) + .ReturnsAsync(rule); + + // Act + var result = await _controller.CreateOrUpdateBusinessRule(rule); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(rule, response.Data); + } + + [Fact] + public async Task DeleteBusinessRuleAsync_ValidRuleId_ReturnsOk() + { + // Arrange + var ruleId = 1; + + _mockRulesService.Setup(service => service.DeleteRuleAsync(ruleId)) + .ReturnsAsync(true); + + // Act + var result = await _controller.DeleteBusinessRule(ruleId); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.True(response.Data); + } + + [Fact] + public async Task GetStatisticsRulesAsync_ValidRequest_ReturnsOk() + { + // Arrange + var rules = new List + { + new StatisticsRuleConfig { RuleId = 1, RuleName = "Production Trend", Enabled = true }, + new StatisticsRuleConfig { RuleId = 2, RuleName = "Efficiency Analysis", Enabled = true } + }; + + _mockRulesService.Setup(service => service.GetStatisticsRulesAsync()) + .ReturnsAsync(rules); + + // Act + var result = await _controller.GetStatisticsRules(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(2, response.Data.Count); + } + + [Fact] + public async Task GetDataRetentionConfigAsync_ValidRequest_ReturnsOk() + { + // Arrange + var retentionConfig = new DataRetentionConfig + { + BusinessDataRetentionDays = 365, + LogDataRetentionDays = 90, + StatisticsDataRetentionDays = 180, + AutoCleanupEnabled = true, + CleanupSchedule = "0 2 * * *" // Daily at 2 AM + }; + + _mockSystemService.Setup(service => service.GetDataRetentionConfigAsync()) + .ReturnsAsync(retentionConfig); + + // Act + var result = await _controller.GetDataRetentionConfig(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(365, response.Data.BusinessDataRetentionDays); + } + + [Fact] + public async Task GetDashboardConfigAsync_ValidRequest_ReturnsOk() + { + // Arrange + var dashboardConfig = new DashboardConfig + { + RefreshInterval = 30, + EnableRealTimeUpdates = true, + DefaultTimeRange = "7d", + AvailableTimeRanges = new List { "1d", "7d", "30d", "90d" }, + AvailableWidgets = new List { "production_chart", "efficiency_chart", "device_status" } + }; + + _mockSystemService.Setup(service => service.GetDashboardConfigAsync()) + .ReturnsAsync(dashboardConfig); + + // Act + var result = await _controller.GetDashboardConfig(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.Equal(30, response.Data.RefreshInterval); + } + + [Fact] + public async Task ValidateConfigurationAsync_ValidConfig_ReturnsOk() + { + // Arrange + var config = new SystemConfiguration { DailyProductionTarget = 100 }; + + var validationResult = new ValidationResult + { + IsValid = true, + Errors = new List(), + Warnings = new List() + }; + + _mockSystemService.Setup(service => service.ValidateConfigurationAsync(config)) + .ReturnsAsync(validationResult); + + // Act + var result = await _controller.ValidateConfiguration(config); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType>(okResult.Value); + Assert.True(response.Success); + Assert.True(response.Data.IsValid); + } + + [Fact] + public async Task ExportConfigurationAsync_ValidRequest_ReturnsFile() + { + // Arrange + var config = new SystemConfiguration + { + DailyProductionTarget = 100, + EnableProduction = true, + EnableAlerts = true + }; + + _mockSystemService.Setup(service => service.GetSystemConfigurationAsync()) + .ReturnsAsync(config); + + // Act + var result = await _controller.ExportConfiguration(); + + // Assert + var fileResult = Assert.IsType(result); + Assert.Contains("system-configuration.json", fileResult.FileDownloadName); + } + } +} \ No newline at end of file diff --git a/Haoliang.Tests/DeviceStateMachineTests.cs b/Haoliang.Tests/DeviceStateMachineTests.cs new file mode 100644 index 0000000..e68dac1 --- /dev/null +++ b/Haoliang.Tests/DeviceStateMachineTests.cs @@ -0,0 +1,525 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Moq; +using Haoliang.Core.Services; +using Haoliang.Data.Repositories; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.System; + +namespace Haoliang.Tests.Services +{ + public class DeviceStateMachineTests + { + private readonly Mock _mockDeviceRepository; + private readonly Mock _mockCollectionService; + private readonly Mock _mockAlarmService; + private readonly Mock _mockCacheService; + private readonly DeviceStateMachine _deviceStateMachine; + + public DeviceStateMachineTests() + { + _mockDeviceRepository = new Mock(); + _mockCollectionService = new Mock(); + _mockAlarmService = new Mock(); + _mockCacheService = new Mock(); + + _deviceStateMachine = new DeviceStateMachine( + _mockDeviceRepository.Object, + _mockCollectionService.Object, + _mockAlarmService.Object, + _mockCacheService.Object + ); + } + + [Fact] + public async Task GetCurrentState_DeviceInCache_ReturnsState() + { + // Arrange + var deviceId = 1; + var expectedState = DeviceState.Running; + + // Setup device context in cache + var deviceContext = new DeviceStateContext + { + CurrentState = expectedState, + PreviousState = DeviceState.Idle, + StateChangedAt = DateTime.UtcNow + }; + + // This would require mocking the internal dictionary + // For this test, we'll simulate the initial state + await _deviceStateMachine.StartAsync(CancellationToken.None); + await _deviceStateMachine.ForceStateAsync(deviceId, expectedState, "Test setup"); + + // Act + var result = _deviceStateMachine.GetCurrentState(deviceId); + + // Assert + Assert.Equal(expectedState, result); + } + + [Fact] + public async Task CanTransitionAsync_ValidTransition_ReturnsTrue() + { + // Arrange + var deviceId = 1; + var fromState = DeviceState.Idle; + var targetState = DeviceState.Running; + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup"); + + // Act + var result = await _deviceStateMachine.CanTransitionAsync(deviceId, targetState); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task CanTransitionAsync_InvalidTransition_ReturnsFalse() + { + // Arrange + var deviceId = 1; + var fromState = DeviceState.Running; + var targetState = DeviceState.Offline; + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup"); + + // Act + var result = await _deviceStateMachine.CanTransitionAsync(deviceId, targetState); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task TransitionToStateAsync_ValidTransition_TransitionsState() + { + // Arrange + var deviceId = 1; + var fromState = DeviceState.Idle; + var targetState = DeviceState.Running; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device", Status = DeviceStatus.Idle }; + var currentStatus = new DeviceCurrentStatus { DeviceId = deviceId, Status = DeviceStatus.Idle, Runtime = TimeSpan.Zero }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId)) + .ReturnsAsync(currentStatus); + _mockDeviceRepository.Setup(repo => repo.UpdateDeviceAsync(device)) + .Returns(Task.CompletedTask); + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup"); + + // Act + var result = await _deviceStateMachine.TransitionToStateAsync(deviceId, targetState); + + // Assert + Assert.True(result.Success); + Assert.Equal(fromState, result.FromState); + Assert.Equal(targetState, result.ToState); + Assert.Equal($"Successfully transitioned from {fromState} to {targetState}", result.Message); + } + + [Fact] + public async Task TransitionToStateAsync_InvalidTransition_ReturnsFailure() + { + // Arrange + var deviceId = 1; + var fromState = DeviceState.Running; + var targetState = DeviceState.Offline; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup"); + + // Act + var result = await _deviceStateMachine.TransitionToStateAsync(deviceId, targetState); + + // Assert + Assert.False(result.Success); + Assert.Contains($"Cannot transition from {fromState} to {targetState}", result.Message); + } + + [Fact] + public async Task TriggerEventAsync_ValidEvent_TransitionsState() + { + // Arrange + var deviceId = 1; + var deviceEvent = DeviceEvent.Start; + var targetState = DeviceState.Running; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device", EnableProduction = true }; + var currentStatus = new DeviceCurrentStatus { DeviceId = deviceId, Status = DeviceStatus.Online, Runtime = TimeSpan.Zero }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId)) + .ReturnsAsync(currentStatus); + _mockDeviceRepository.Setup(repo => repo.UpdateDeviceAsync(device)) + .Returns(Task.CompletedTask); + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup"); + + // Act + var result = await _deviceStateMachine.TriggerEventAsync(deviceId, deviceEvent); + + // Assert + Assert.True(result.Success); + Assert.Equal(DeviceState.Idle, result.FromState); + Assert.Equal(targetState, result.ToState); + } + + [Fact] + public async Task TriggerEventAsync_InvalidEvent_ReturnsFailure() + { + // Arrange + var deviceId = 1; + var deviceEvent = DeviceEvent.Stop; // Cannot stop when not running + + var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup"); + + // Act + var result = await _deviceStateMachine.TriggerEventAsync(deviceId, deviceEvent); + + // Assert + Assert.False(result.Success); + Assert.Contains($"No event handler configured for {deviceEvent} in state DeviceState.Idle", result.Message); + } + + [Fact] + public async Task ForceStateAsync_ValidForce_TransitionsState() + { + // Arrange + var deviceId = 1; + var targetState = DeviceState.Running; + var reason = "Emergency override"; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + + // Act + var result = await _deviceStateMachine.ForceStateAsync(deviceId, targetState, reason); + + // Assert + Assert.True(result.Success); + Assert.Equal($"Forced state transition to {targetState}: {reason}", result.Message); + } + + [Fact] + public async Task ValidateStateAsync_ValidState_ReturnsValid() + { + // Arrange + var deviceId = 1; + var device = new CNCDevice { + Id = deviceId, + Name = "Test Device", + Status = DeviceStatus.Idle, + EnableProduction = true + }; + var currentStatus = new DeviceCurrentStatus { + DeviceId = deviceId, + Status = DeviceStatus.Idle, + Runtime = TimeSpan.FromHours(1) + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId)) + .ReturnsAsync(currentStatus); + + // Initialize device state + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup"); + + // Act + var result = await _deviceStateMachine.ValidateStateAsync(deviceId); + + // Assert + Assert.True(result.IsValid); + Assert.Equal(DeviceState.Idle, result.CurrentState); + Assert.Empty(result.Issues); + } + + [Fact] + public async Task ValidateStateAsync_StateMismatch_ReturnsInvalid() + { + // Arrange + var deviceId = 1; + var device = new CNCDevice { + Id = deviceId, + Name = "Test Device", + Status = DeviceStatus.Running, // Status doesn't match state + EnableProduction = true + }; + var currentStatus = new DeviceCurrentStatus { + DeviceId = deviceId, + Status = DeviceStatus.Running, + Runtime = TimeSpan.FromHours(1) + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId)) + .ReturnsAsync(currentStatus); + + // Initialize device state as Idle but status is Running + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup"); + + // Act + var result = await _deviceStateMachine.ValidateStateAsync(deviceId); + + // Assert + Assert.False(result.IsValid); + Assert.Contains("State mismatch", result.Issues.First()); + } + + [Fact] + public void RegisterStateHandler_ValidHandler_RegistersHandler() + { + // Arrange + var handlerMock = new Mock>(); + + // Act + _deviceStateMachine.RegisterStateHandler((deviceId, fromState, toState) => handlerMock.Object(deviceId, fromState, toState)); + + // Assert + // This test would need to verify the handler was registered + // For now, we'll just ensure it doesn't throw + Assert.True(true); + } + + [Fact] + public void UnregisterStateHandler_ValidHandler_UnregistersHandler() + { + // Arrange + var handlerMock = new Mock>(); + + // Act + _deviceStateMachine.RegisterStateHandler((deviceId, fromState, toState) => handlerMock.Object(deviceId, fromState, toState)); + _deviceStateMachine.UnregisterStateHandler((deviceId, fromState, toState) => handlerMock.Object(deviceId, fromState, toState)); + + // Assert + // This test would need to verify the handler was unregistered + // For now, we'll just ensure it doesn't throw + Assert.True(true); + } + + [Fact] + public async Task GetStateHistoryAsync_DeviceWithHistory_ReturnsHistory() + { + // Arrange + var deviceId = 1; + var history = new List + { + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Idle, ChangedAt = DateTime.Now.AddHours(-2) }, + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Running, ChangedAt = DateTime.Now.AddHours(-1) } + }; + + _mockDeviceRepository.Setup(repo => repo.GetDeviceStateHistoryAsync(deviceId, null, null)) + .ReturnsAsync(history); + + // Initialize current state + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Stopped, "Test setup"); + + // Act + var result = await _deviceStateMachine.GetStateHistoryAsync(deviceId); + + // Assert + Assert.Equal(3, result.Count); // History entries + current state + Assert.Equal(DeviceState.Idle, result[0].State); + Assert.Equal(DeviceState.Running, result[1].State); + Assert.Equal(DeviceState.Stopped, result[2].State); + } + + [Fact] + public async Task GetStateStatisticsAsync_DeviceWithHistory_ReturnsStatistics() + { + // Arrange + var deviceId = 1; + var history = new List + { + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Idle, ChangedAt = DateTime.Now.AddHours(-2) }, + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Running, ChangedAt = DateTime.Now.AddHours(-1) } + }; + + _mockDeviceRepository.Setup(repo => repo.GetDeviceStateHistoryAsync(deviceId, null, null)) + .ReturnsAsync(history); + + // Initialize current state + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Stopped, "Test setup"); + + // Act + var result = await _deviceStateMachine.GetStateStatisticsAsync(deviceId); + + // Assert + Assert.NotNull(result); + Assert.Equal(deviceId, result.DeviceId); + Assert.True(result.StateTransitions > 0); + Assert.True(result.StateDurations.ContainsKey(DeviceState.Idle)); + Assert.True(result.StateDurations.ContainsKey(DeviceState.Running)); + Assert.True(result.StateDurations.ContainsKey(DeviceState.Stopped)); + Assert.True(result.StateCounts.ContainsKey(DeviceState.Idle)); + Assert.True(result.StateCounts.ContainsKey(DeviceState.Running)); + Assert.True(result.StateCounts.ContainsKey(DeviceState.Stopped)); + } + + [Fact] + public async Task GetStateStatisticsAsync_WithDateFilter_ReturnsFilteredStatistics() + { + // Arrange + var deviceId = 1; + var filterStartDate = DateTime.Now.AddDays(-1); + var filterEndDate = DateTime.Now; + + var history = new List + { + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Idle, ChangedAt = DateTime.Now.AddDays(-2) }, // Outside filter + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Running, ChangedAt = DateTime.Now.AddHours(-12) }, // Within filter + new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Stopped, ChangedAt = DateTime.Now.AddHours(-6) } // Within filter + }; + + _mockDeviceRepository.Setup(repo => repo.GetDeviceStateHistoryAsync(deviceId, filterStartDate, filterEndDate)) + .ReturnsAsync(history); + + // Initialize current state + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Stopped, "Test setup"); + + // Act + var result = await _deviceStateMachine.GetStateStatisticsAsync(deviceId, filterStartDate, filterEndDate); + + // Assert + Assert.NotNull(result); + Assert.Equal(deviceId, result.DeviceId); + Assert.Equal(filterStartDate, result.PeriodStart); + Assert.Equal(filterEndDate, result.PeriodEnd); + } + + [Fact] + public async Task TranslateStatusToDeviceState_ValidStatus_ReturnsState() + { + // Arrange + var currentStatus = new DeviceCurrentStatus { Status = DeviceStatus.Running }; + + // Act + // This method would need to be made public or tested through another method + // For now, we'll test the translation logic through a state transition + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(1)) + .ReturnsAsync(currentStatus); + + // Initialize device state and check if it matches the status + await _deviceStateMachine.ForceStateAsync(1, DeviceState.Unknown, "Test setup"); + var result = _deviceStateMachine.GetCurrentState(1); + + // Assert + // The state should be Running based on the status + Assert.Equal(DeviceState.Unknown, result); // Initially unknown until transition + } + + [Fact] + public async Task TranslateStateToDeviceStatus_ValidState_ReturnsStatus() + { + // Arrange + var state = DeviceState.Running; + + // Act + // This would test the reverse translation + // We can test it by verifying the device status is updated correctly + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockDeviceRepository.Setup(repo => repo.UpdateDeviceAsync(device)) + .Returns(Task.CompletedTask); + + // Transition to Running state and check if device status is updated + await _deviceStateMachine.TransitionToStateAsync(1, state); + + // Assert + // The device status should be updated to Running + Assert.Equal(DeviceStatus.Running, device.Status); + } + + [Fact] + public async Task HandleInvalidStateAsync_InvalidState_TransitionsToSafeState() + { + // Arrange + var deviceId = 1; + var validationResult = new DeviceValidationResult + { + DeviceId = deviceId, + IsValid = false, + Issues = new List { "State mismatch" }, + CurrentState = DeviceState.Error + }; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + + // Act + // This method is private, so we test through ValidateStateAsync which calls it + await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Error, "Test setup"); + var result = await _deviceStateMachine.ValidateStateAsync(deviceId); + + // The state machine should handle the invalid state + // We expect the state to remain Error since it's already a safe state for errors + Assert.Equal(DeviceState.Error, _deviceStateMachine.GetCurrentState(deviceId)); + } + + [Fact] + public async Task StartAsync_InitializesDeviceStates() + { + // Arrange + var devices = new List + { + new CNCDevice { Id = 1, Name = "Device 1" }, + new CNCDevice { Id = 2, Name = "Device 2" } + }; + + var device1Status = new DeviceCurrentStatus { DeviceId = 1, Status = DeviceStatus.Online, Runtime = TimeSpan.Zero }; + var device2Status = new DeviceCurrentStatus { DeviceId = 2, Status = DeviceStatus.Running, Runtime = TimeSpan.FromHours(1) }; + + _mockDeviceRepository.Setup(repo => repo.GetAllDevicesAsync()) + .ReturnsAsync(devices); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(1)) + .ReturnsAsync(device1Status); + _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(2)) + .ReturnsAsync(device2Status); + + // Act + await _deviceStateMachine.StartAsync(CancellationToken.None); + + // Assert + // Allow time for async initialization + await Task.Delay(100); + + // Check if states are initialized based on device status + Assert.Equal(DeviceState.Online, _deviceStateMachine.GetCurrentState(1)); + Assert.Equal(DeviceState.Running, _deviceStateMachine.GetCurrentState(2)); + } + } +} \ No newline at end of file diff --git a/Haoliang.Tests/Haoliang.Tests.csproj b/Haoliang.Tests/Haoliang.Tests.csproj index 338639c..ecae91f 100644 --- a/Haoliang.Tests/Haoliang.Tests.csproj +++ b/Haoliang.Tests/Haoliang.Tests.csproj @@ -19,6 +19,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + + + diff --git a/Haoliang.Tests/ProductionStatisticsServiceTests.cs b/Haoliang.Tests/ProductionStatisticsServiceTests.cs new file mode 100644 index 0000000..78eead8 --- /dev/null +++ b/Haoliang.Tests/ProductionStatisticsServiceTests.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Moq; +using Haoliang.Core.Services; +using Haoliang.Data.Repositories; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Models.System; + +namespace Haoliang.Tests.Services +{ + public class ProductionStatisticsServiceTests + { + private readonly Mock _mockProductionRepository; + private readonly Mock _mockDeviceRepository; + private readonly Mock _mockSystemRepository; + private readonly Mock _mockAlarmRepository; + private readonly Mock _mockCollectionRepository; + private readonly ProductionStatisticsService _statisticsService; + + public ProductionStatisticsServiceTests() + { + _mockProductionRepository = new Mock(); + _mockDeviceRepository = new Mock(); + _mockSystemRepository = new Mock(); + _mockAlarmRepository = new Mock(); + _mockCollectionRepository = new Mock(); + + _statisticsService = new ProductionStatisticsService( + _mockProductionRepository.Object, + _mockDeviceRepository.Object, + _mockSystemRepository.Object, + _mockAlarmRepository.Object, + _mockCollectionRepository.Object + ); + } + + [Fact] + public async Task CalculateProductionTrendsAsync_ValidDeviceAndDates_ReturnsTrendAnalysis() + { + // Arrange + var deviceId = 1; + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device" }; + var productionRecords = new List + { + new ProductionRecord { Id = 1, DeviceId = deviceId, Created = startDate, Quantity = 100, TargetQuantity = 120 }, + new ProductionRecord { Id = 2, DeviceId = deviceId, Created = startDate.AddDays(1), Quantity = 110, TargetQuantity = 120 }, + new ProductionRecord { Id = 3, DeviceId = deviceId, Created = startDate.AddDays(2), Quantity = 130, TargetQuantity = 120 } + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(deviceId, startDate, endDate)) + .ReturnsAsync(productionRecords); + _mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync()) + .ReturnsAsync(new SystemConfiguration { DailyProductionTarget = 100 }); + + // Act + var result = await _statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate); + + // Assert + Assert.NotNull(result); + Assert.Equal(deviceId, result.DeviceId); + Assert.Equal(device.Name, result.DeviceName); + Assert.Equal(startDate, result.PeriodStart); + Assert.Equal(endDate, result.PeriodEnd); + Assert.Equal(340, result.TotalProduction); + Assert.Equal(3, result.AverageDailyProduction); + Assert.Equal(3, result.DailyData.Count); + } + + [Fact] + public async Task CalculateProductionTrendsAsync_InvalidDevice_ThrowsKeyNotFoundException() + { + // Arrange + var deviceId = 999; + var startDate = DateTime.Now.AddDays(-7); + var endDate = DateTime.Now; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync((CNCDevice)null); + + // Act & Assert + await Assert.ThrowsAsync(() => + _statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate)); + } + + [Fact] + public async Task GenerateProductionReportAsync_ValidFilter_ReturnsProductionReport() + { + // Arrange + var filter = new ReportFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + ReportType = ReportType.Daily + }; + + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + var records = new List + { + new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now, Quantity = 100, TargetQuantity = 120, IsGood = true }, + new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now, Quantity = 50, TargetQuantity = 60, IsGood = true } + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByFilterAsync(filter)) + .ReturnsAsync(records); + _mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync()) + .ReturnsAsync(new SystemConfiguration { DailyProductionTarget = 100 }); + + // Act + var result = await _statisticsService.GenerateProductionReportAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Equal(ReportType.Daily, result.ReportType); + Assert.Equal(2, result.SummaryItems.Count); + + var summary = result.SummaryItems.First(); + Assert.Equal(1, summary.DeviceId); + Assert.Equal("Test Device", summary.DeviceName); + Assert.Equal(150, summary.Quantity); + Assert.Equal(180, summary.TargetQuantity); + Assert.Equal(83.33m, summary.Efficiency); + } + + [Fact] + public async Task GetDashboardSummaryAsync_ValidFilter_ReturnsDashboardSummary() + { + // Arrange + var filter = new DashboardFilter + { + Date = DateTime.Today, + IncludeAlerts = true + }; + + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + var deviceSummaries = new List + { + new DeviceSummary { DeviceId = 1, DeviceName = "Test Device", TodayProduction = 100, Efficiency = 85, QualityRate = 98 } + }; + + var alertSummaries = new List + { + new AlertSummary { AlertId = 1, DeviceName = "Test Device", AlertType = AlertType.DeviceOffline, Message = "Device offline" } + }; + + _mockDeviceRepository.Setup(repo => repo.GetAllDevicesAsync()) + .ReturnsAsync(new List { device }); + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockCollectionRepository.Setup(repo => GetDeviceCurrentStatusAsync(1)) + .ReturnsAsync(new DeviceCurrentStatus { Status = DeviceStatus.Online, Runtime = TimeSpan.FromHours(8) }); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateAsync(1, DateTime.Today)) + .ReturnsAsync(new List()); + _mockAlarmRepository.Setup(repo => GetActiveAlertsByDeviceAsync(1)) + .ReturnsAsync(new List { new Alert { Id = 1, DeviceId = 1, AlertType = "DeviceOffline", Message = "Device offline" } }); + + // Act + var result = await _statisticsService.GetDashboardSummaryAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Equal(DateTime.Now.Date, result.GeneratedAt.Date); + Assert.Equal(1, result.TotalDevices); + Assert.Equal(1, result.ActiveDevices); + Assert.Equal(0, result.OfflineDevices); + Assert.Equal(1, result.DeviceSummaries.Count); + Assert.Equal(1, result.ActiveAlerts.Count); + } + + [Fact] + public async Task CalculateOeeAsync_ValidDeviceAndDate_ReturnsOeeMetrics() + { + // Arrange + var deviceId = 1; + var date = DateTime.Today; + + var device = new CNCDevice { Id = deviceId, Name = "Test Device", IdealCycleTime = 2 }; + var records = new List + { + new ProductionRecord { Id = 1, DeviceId = deviceId, Created = date, Quantity = 50, TargetQuantity = 60, IsGood = true } + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId)) + .ReturnsAsync(device); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateAsync(deviceId, date)) + .ReturnsAsync(records); + _mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync()) + .ReturnsAsync(new SystemConfiguration { DailyWorkingHours = TimeSpan.FromHours(8) }); + _mockCollectionRepository.Setup(repo => GetDeviceStatusHistoryAsync(deviceId, date, date)) + .ReturnsAsync(new List()); + + // Act + var result = await _statisticsService.CalculateOeeAsync(deviceId, date); + + // Assert + Assert.NotNull(result); + Assert.Equal(deviceId, result.DeviceId); + Assert.Equal(device.Name, result.DeviceName); + Assert.Equal(date, result.Date); + Assert.True(result.Availability >= 0 && result.Availability <= 100); + Assert.True(result.Performance >= 0 && result.Performance <= 100); + Assert.True(result.Quality >= 0 && result.Quality <= 100); + Assert.True(result.Oee >= 0 && result.Oee <= 100); + } + + [Fact] + public async Task GenerateProductionForecastAsync_ValidFilter_ReturnsProductionForecast() + { + // Arrange + var filter = new ForecastFilter + { + DeviceId = 1, + DaysToForecast = 7, + Model = ForecastModel.Linear, + HistoricalDataStart = DateTime.Now.AddDays(-30), + HistoricalDataEnd = DateTime.Now + }; + + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + var historicalData = new List + { + new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-30), Quantity = 100 }, + new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now.AddDays(-29), Quantity = 110 }, + new ProductionRecord { Id = 3, DeviceId = 1, Created = DateTime.Now.AddDays(-28), Quantity = 105 } + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.HistoricalDataStart, filter.HistoricalDataEnd)) + .ReturnsAsync(historicalData); + + // Act + var result = await _statisticsService.GenerateProductionForecastAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Equal(filter.DeviceId, result.DeviceId); + Assert.Equal(device.Name, result.DeviceName); + Assert.Equal(filter.Model, result.ModelUsed); + Assert.Equal(7, result.DailyForecasts.Count); + + // Check that forecasts have reasonable values + foreach (var forecast in result.DailyForecasts) + { + Assert.True(forecast.ForecastedQuantity >= 0); + Assert.True(forecast.ConfidenceLower <= forecast.ForecastedQuantity); + Assert.True(forecast.ConfidenceUpper >= forecast.ForecastedQuantity); + } + } + + [Fact] + public async Task DetectProductionAnomaliesAsync_ValidFilter_ReturnsAnomalyAnalysis() + { + // Arrange + var filter = new AnomalyFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + MinSeverity = AnomalySeverity.Medium + }; + + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + var records = new List + { + new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-2), Quantity = 100 }, + new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now.AddDays(-1), Quantity = 30 }, // Big drop + new ProductionRecord { Id = 3, DeviceId = 1, Created = DateTime.Now, Quantity = 95 } + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.StartDate, filter.EndDate)) + .ReturnsAsync(records); + + // Act + var result = await _statisticsService.DetectProductionAnomaliesAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Equal(1, result.DeviceIds.Count); + Assert.True(result.Anomalies.Count >= 1); // Should detect the production drop + + var anomaly = result.Anomalies.FirstOrDefault(a => a.Type == AnomalyType.ProductionDrop); + Assert.NotNull(anomaly); + Assert.Equal(AnomalySeverity.High, anomaly.Severity); + } + + [Fact] + public async Task GetEfficiencyMetricsAsync_ValidFilter_ReturnsEfficiencyMetrics() + { + // Arrange + var filter = new EfficiencyFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + Metrics = EfficiencyMetric.Oee + }; + + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + var records = new List + { + new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-1), Quantity = 100, TargetQuantity = 120, IsGood = true } + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockDeviceRepository.Setup(repo => repo.GetAllActiveDevicesAsync()) + .ReturnsAsync(new List { device }); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.StartDate, filter.EndDate)) + .ReturnsAsync(records); + + // Act + var result = await _statisticsService.CalculateEfficiencyMetricsAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Single(result.DeviceIds); + Assert.Equal(1, result.DeviceIds.First()); + Assert.True(result.Availability >= 0 && result.Availability <= 100); + Assert.True(result.Performance >= 0 && result.Performance <= 100); + Assert.True(result.Quality >= 0 && result.Quality <= 100); + Assert.True(result.Oee >= 0 && result.Oee <= 100); + } + + [Fact] + public async Task PerformQualityAnalysisAsync_ValidFilter_ReturnsQualityAnalysis() + { + // Arrange + var filter = new QualityFilter + { + DeviceIds = new List { 1 }, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now, + MetricType = QualityMetricType.DefectRate + }; + + var device = new CNCDevice { Id = 1, Name = "Test Device" }; + var records = new List + { + new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now, Quantity = 100, TargetQuantity = 120, IsGood = true }, + new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now, Quantity = 20, TargetQuantity = 30, IsGood = false } // Defects + }; + + _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1)) + .ReturnsAsync(device); + _mockDeviceRepository.Setup(repo => repo.GetAllActiveDevicesAsync()) + .ReturnsAsync(new List { device }); + _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByFilterAsync(It.IsAny())) + .ReturnsAsync(records); + + // Act + var result = await _statisticsService.PerformQualityAnalysisAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Single(result.DeviceIds); + Assert.Equal(1, result.TotalProduced); + Assert.Equal(0.8m, result.QualityRate); // 80% quality rate + Assert.Equal(0.2m, result.DefectRate); // 20% defect rate + Assert.True(result.QualityMetrics.Count > 0); + } + } +} \ No newline at end of file diff --git a/Haoliang.Tests/RealTimeServiceTests.cs b/Haoliang.Tests/RealTimeServiceTests.cs new file mode 100644 index 0000000..ce2facf --- /dev/null +++ b/Haoliang.Tests/RealTimeServiceTests.cs @@ -0,0 +1,445 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Moq; +using Microsoft.AspNetCore.SignalR; +using Haoliang.Core.Services; +using Haoliang.Models.Models.Device; +using Haoliang.Models.Models.Production; +using Haoliang.Models.Models.System; + +namespace Haoliang.Tests.Services +{ + public class RealTimeServiceTests + { + private readonly Mock> _mockHubContext; + private readonly Mock _mockDeviceCollectionService; + private readonly Mock _mockProductionService; + private readonly Mock _mockAlarmService; + private readonly Mock _mockCacheService; + private readonly RealTimeService _realTimeService; + + public RealTimeServiceTests() + { + _mockHubContext = new Mock>(); + _mockDeviceCollectionService = new Mock(); + _mockProductionService = new Mock(); + _mockAlarmService = new Mock(); + _mockCacheService = new Mock(); + + _realTimeService = new RealTimeService( + _mockHubContext.Object, + _mockDeviceCollectionService.Object, + _mockProductionService.Object, + _mockAlarmService.Object, + _mockCacheService.Object + ); + } + + [Fact] + public async Task ConnectClientAsync_ValidConnection_ConnectsClient() + { + // Arrange + var connectionId = "test-connection-id"; + var userId = "test-user-id"; + var clientType = "web"; + + // Act + await _realTimeService.ConnectClientAsync(connectionId, userId, clientType); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Client(connectionId) + .SendAsync("ClientConnected", + It.Is(o => + dynamic obj = o && + obj.GetType().GetProperty("ClientId").GetValue(obj) == connectionId && + obj.GetType().GetProperty("UserId").GetValue(obj) == userId && + obj.GetType().GetProperty("ClientType").GetValue(obj) == clientType), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task DisconnectClientAsync_ValidConnection_DisconnectsClient() + { + // Arrange + var connectionId = "test-connection-id"; + + // Act + await _realTimeService.DisconnectClientAsync(connectionId); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.AllExcept(connectionId) + .SendAsync("ClientDisconnected", + It.IsAny(), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task JoinDeviceGroupAsync_ValidConnection_JoinsGroup() + { + // Arrange + var connectionId = "test-connection-id"; + var deviceId = 1; + var deviceStatus = new DeviceCurrentStatus { + DeviceId = deviceId, + Status = DeviceStatus.Online, + CurrentProgram = "Test Program" + }; + + _mockDeviceCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId)) + .ReturnsAsync(deviceStatus); + + // Act + await _realTimeService.JoinDeviceGroupAsync(connectionId, deviceId); + + // Assert + _mockHubContext.Verify(hub => hub.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}", It.IsAny()), Times.Once); + _mockHubContext.Verify(hub => hub.Clients.Client(connectionId) + .SendAsync("DeviceStatusUpdated", + It.Is(o => + dynamic obj = o && + obj.GetType().GetProperty("DeviceId").GetValue(obj) == deviceId), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task LeaveDeviceGroupAsync_ValidConnection_LeavesGroup() + { + // Arrange + var connectionId = "test-connection-id"; + var deviceId = 1; + + // Act + await _realTimeService.LeaveDeviceGroupAsync(connectionId, deviceId); + + // Assert + _mockHubContext.Verify(hub => hub.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}", It.IsAny()), Times.Once); + } + + [Fact] + public async Task JoinDashboardGroupAsync_ValidConnection_JoinsGroup() + { + // Arrange + var connectionId = "test-connection-id"; + var dashboardId = "dashboard-1"; + var dashboardUpdate = new DashboardUpdate { + Timestamp = DateTime.UtcNow, + TotalDevices = 10, + ActiveDevices = 8, + TotalProductionToday = 1000 + }; + + _mockCacheService.Setup(cache => cache.GetOrSetDashboardSummaryAsync(It.IsAny(), It.IsAny>>())) + .ReturnsAsync(new DashboardSummary { + TotalDevices = 10, + ActiveDevices = 8, + TotalProductionToday = 1000 + }); + + // Act + await _realTimeService.JoinDashboardGroupAsync(connectionId, dashboardId); + + // Assert + _mockHubContext.Verify(hub => hub.Groups.AddToGroupAsync(connectionId, $"dashboard_{dashboardId}", It.IsAny()), Times.Once); + _mockHubContext.Verify(hub => hub.Clients.Client(connectionId) + .SendAsync("DashboardUpdated", + It.Is(o => + dynamic obj = o && + obj.GetType().GetProperty("DashboardId").GetValue(obj)?.ToString() == dashboardId), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task LeaveDashboardGroupAsync_ValidConnection_LeavesGroup() + { + // Arrange + var connectionId = "test-connection-id"; + var dashboardId = "dashboard-1"; + + // Act + await _realTimeService.LeaveDashboardGroupAsync(connectionId, dashboardId); + + // Assert + _mockHubContext.Verify(hub => hub.Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{dashboardId}", It.IsAny()), Times.Once); + } + + [Fact] + public async Task BroadcastDeviceStatusAsync_ValidStatus_BroadcastsToGroups() + { + // Arrange + var statusUpdate = new DeviceStatusUpdate + { + DeviceId = 1, + DeviceName = "Test Device", + Status = DeviceStatus.Running, + CurrentProgram = "Test Program", + Runtime = TimeSpan.FromHours(1), + Timestamp = DateTime.UtcNow + }; + + // Act + await _realTimeService.BroadcastDeviceStatusAsync(statusUpdate); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Group($"device_1") + .SendAsync("DeviceStatusUpdated", + It.Is(s => s.DeviceId == 1 && s.Status == DeviceStatus.Running), + It.IsAny()), Times.Once); + + _mockHubContext.Verify(hub => hub.Clients.Group("dashboard") + .SendAsync("DeviceStatusUpdated", + It.Is(s => s.DeviceId == 1 && s.Status == DeviceStatus.Running), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task BroadcastProductionUpdateAsync_ValidUpdate_BroadcastsToGroups() + { + // Arrange + var productionUpdate = new ProductionUpdate + { + DeviceId = 1, + DeviceName = "Test Device", + Quantity = 100, + Timestamp = DateTime.UtcNow + }; + + // Act + await _realTimeService.BroadcastProductionUpdateAsync(productionUpdate); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Group($"device_1") + .SendAsync("ProductionUpdated", + It.Is(p => p.DeviceId == 1 && p.Quantity == 100), + It.IsAny()), Times.Once); + + _mockHubContext.Verify(hub => hub.Clients.Group("dashboard") + .SendAsync("ProductionUpdated", + It.Is(p => p.DeviceId == 1 && p.Quantity == 100), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task BroadcastAlertAsync_ValidAlert_BroadcastsToRelevantGroups() + { + // Arrange + var alertUpdate = new AlertUpdate + { + DeviceId = 1, + DeviceName = "Test Device", + AlertType = "DeviceError", + Message = "Device error occurred", + Timestamp = DateTime.UtcNow, + IsResolved = false + }; + + // Act + await _realTimeService.BroadcastAlertAsync(alertUpdate); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Group("dashboard") + .SendAsync("AlertUpdated", + It.Is(a => a.DeviceId == 1 && a.AlertType == "DeviceError"), + It.IsAny()), Times.Once); + + _mockHubContext.Verify(hub => hub.Clients.Group("alerts") + .SendAsync("AlertUpdated", + It.Is(a => a.DeviceId == 1 && a.AlertType == "DeviceError"), + It.IsAny()), Times.Once); + + _mockHubContext.Verify(hub => hub.Clients.Group($"device_1") + .SendAsync("AlertUpdated", + It.Is(a => a.DeviceId == 1 && a.AlertType == "DeviceError"), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task SendSystemNotificationAsync_ValidNotification_SendsToNotificationGroup() + { + // Arrange + var notification = new SystemNotification + { + NotificationType = "Info", + Title = "System Update", + Message = "System maintenance scheduled", + Timestamp = DateTime.UtcNow + }; + + // Act + await _realTimeService.SendSystemNotificationAsync(notification); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Group("notifications") + .SendAsync("SystemNotification", + It.Is(n => + n.NotificationType == "Info" && + n.Title == "System Update"), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task SendDashboardUpdateAsync_ValidUpdate_SendsToDashboardGroup() + { + // Arrange + var dashboardUpdate = new DashboardUpdate + { + Timestamp = DateTime.UtcNow, + TotalDevices = 10, + ActiveDevices = 8, + TotalProductionToday = 1000 + }; + + // Act + await _realTimeService.SendDashboardUpdateAsync(dashboardUpdate); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Group("dashboard") + .SendAsync("DashboardUpdated", + It.Is(d => + d.TotalDevices == 10 && + d.ActiveDevices == 8), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task SendCommandToClientAsync_ValidCommand_SendsToClient() + { + // Arrange + var connectionId = "test-connection-id"; + var command = new RealTimeCommand + { + Command = "RefreshData", + Parameters = new { Interval = 5000 }, + Timestamp = DateTime.UtcNow + }; + + // Act + await _realTimeService.SendCommandToClientAsync(connectionId, command); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.Client(connectionId) + .SendAsync("Command", + It.Is(c => + c.Command == "RefreshData" && + c.Parameters.ToString().Contains("Interval")), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task BroadcastCommandAsync_ValidCommand_BroadcastsToAllClients() + { + // Arrange + var command = new RealTimeCommand + { + Command = "SystemShutdown", + Parameters = new { DelayMinutes = 5 }, + Timestamp = DateTime.UtcNow + }; + + // Act + await _realTimeService.BroadcastCommandAsync(command); + + // Assert + _mockHubContext.Verify(hub => hub.Clients.All + .SendAsync("Command", + It.Is(c => + c.Command == "SystemShutdown"), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task GetConnectedClientsCountAsync_ValidClients_ReturnsCount() + { + // Arrange + // This test would need to mock the internal client tracking + // For now, we'll verify the method exists and doesn't throw + var result = await _realTimeService.GetConnectedClientsCountAsync(); + + // Assert + Assert.True(result >= 0); // Should return a non-negative number + } + + [Fact] + public async Task GetConnectedClientsByTypeAsync_ValidType_ReturnsClients() + { + // Arrange + var clientType = "web"; + + // This test would need to mock the internal client tracking + // For now, we'll verify the method exists and doesn't throw + var result = await _realTimeService.GetConnectedClientsByTypeAsync(clientType); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task GetDeviceMonitoringStatusAsync_ValidDevice_ReturnsStatus() + { + // Arrange + var deviceId = 1; + var streamingInfo = new DeviceStreamingInfo + { + DeviceId = deviceId, + IntervalMs = 1000, + StartedAt = DateTime.UtcNow.AddMinutes(-5), + LastUpdate = DateTime.UtcNow.AddMinutes(-1), + IsRunning = true + }; + + // This test would need to mock the internal device streaming tracking + // For now, we'll verify the method exists and doesn't throw + var result = await _realTimeService.GetDeviceMonitoringStatusAsync(deviceId); + + // Assert + Assert.NotNull(result); + Assert.Equal(deviceId, result.DeviceId); + Assert.True(result.IsStreaming); + } + + [Fact] + public async Task StartDeviceStreamingAsync_ValidDevice_StartsStreaming() + { + // Arrange + var deviceId = 1; + var intervalMs = 1000; + + // This test would need to mock the internal device streaming tracking + // and verify that streaming starts + // For now, we'll verify the method exists and doesn't throw + await _realTimeService.StartDeviceStreamingAsync(deviceId, intervalMs); + + // Assert - would need to verify streaming started + Assert.True(true); // Placeholder assertion + } + + [Fact] + public async Task StopDeviceStreamingAsync_ValidDevice_StopsStreaming() + { + // Arrange + var deviceId = 1; + + // This test would need to mock the internal device streaming tracking + // and verify that streaming stops + // For now, we'll verify the method exists and doesn't throw + await _realTimeService.StopDeviceStreamingAsync(deviceId); + + // Assert - would need to verify streaming stopped + Assert.True(true); // Placeholder assertion + } + + [Fact] + public async Task GetActiveStreamingDevicesAsync_ReturnsStreamingDevices() + { + // This test would need to mock the internal device streaming tracking + // For now, we'll verify the method exists and doesn't throw + var result = await _realTimeService.GetActiveStreamingDevicesAsync(); + + // Assert + Assert.NotNull(result); + Assert.IsType>(result); + } + } +} \ No newline at end of file diff --git a/Haoliang.Tests/obj/Debug/net6.0/Haoliang.Tests.assets.cache b/Haoliang.Tests/obj/Debug/net6.0/Haoliang.Tests.assets.cache index 4525f83..e54e601 100644 Binary files a/Haoliang.Tests/obj/Debug/net6.0/Haoliang.Tests.assets.cache and b/Haoliang.Tests/obj/Debug/net6.0/Haoliang.Tests.assets.cache differ diff --git a/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.dgspec.json b/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.dgspec.json index 186baa7..f43b457 100644 --- a/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.dgspec.json +++ b/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.dgspec.json @@ -101,9 +101,21 @@ "net6.0": { "targetAlias": "net6.0", "dependencies": { + "Microsoft.EntityFrameworkCore": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Design": { + "target": "Package", + "version": "[7.0.2, )" + }, + "Microsoft.EntityFrameworkCore.Tools": { + "target": "Package", + "version": "[7.0.2, )" + }, "Pomelo.EntityFrameworkCore.MySql": { "target": "Package", - "version": "[6.0.32, )" + "version": "[7.0.0, )" } }, "imports": [ diff --git a/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.props b/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.props index 7f4a751..a091c38 100644 --- a/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.props +++ b/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.props @@ -23,5 +23,6 @@ /root/.nuget/packages/xunit.analyzers/0.10.0 /root/.nuget/packages/newtonsoft.json/9.0.1 + /root/.nuget/packages/microsoft.entityframeworkcore.tools/7.0.2 \ No newline at end of file diff --git a/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.targets b/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.targets index 48551c9..3eaef45 100644 --- a/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.targets +++ b/Haoliang.Tests/obj/Haoliang.Tests.csproj.nuget.g.targets @@ -2,6 +2,7 @@ + diff --git a/Haoliang.Tests/obj/project.assets.json b/Haoliang.Tests/obj/project.assets.json index 5fa6790..c523920 100644 --- a/Haoliang.Tests/obj/project.assets.json +++ b/Haoliang.Tests/obj/project.assets.json @@ -8,6 +8,15 @@ "build/netstandard1.0/coverlet.collector.targets": {} } }, + "Humanizer.Core/2.14.1": { + "type": "package", + "compile": { + "lib/net6.0/Humanizer.dll": {} + }, + "runtime": { + "lib/net6.0/Humanizer.dll": {} + } + }, "Microsoft.CodeCoverage/16.11.0": { "type": "package", "compile": { @@ -85,6 +94,24 @@ "lib/netstandard2.0/_._": {} } }, + "Microsoft.EntityFrameworkCore.Design/7.0.2": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.EntityFrameworkCore.Relational": "7.0.2", + "Microsoft.Extensions.DependencyModel": "7.0.0", + "Mono.TextTemplating": "2.2.1" + }, + "compile": { + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll": {} + }, + "build": { + "build/net6.0/_._": {} + } + }, "Microsoft.EntityFrameworkCore.InMemory/6.0.32": { "type": "package", "dependencies": { @@ -110,6 +137,18 @@ "lib/net6.0/Microsoft.EntityFrameworkCore.Relational.dll": {} } }, + "Microsoft.EntityFrameworkCore.Tools/7.0.2": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": "7.0.2" + }, + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/_._": {} + } + }, "Microsoft.Extensions.Caching.Abstractions/7.0.0": { "type": "package", "dependencies": { @@ -186,6 +225,22 @@ "buildTransitive/net6.0/_._": {} } }, + "Microsoft.Extensions.DependencyModel/7.0.0": { + "type": "package", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0", + "System.Text.Json": "7.0.0" + }, + "compile": { + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, "Microsoft.Extensions.Logging/7.0.0": { "type": "package", "dependencies": { @@ -542,6 +597,18 @@ "ref/netstandard1.3/Microsoft.Win32.Primitives.dll": {} } }, + "Mono.TextTemplating/2.2.1": { + "type": "package", + "dependencies": { + "System.CodeDom": "4.4.0" + }, + "compile": { + "lib/netstandard2.0/Mono.TextTemplating.dll": {} + }, + "runtime": { + "lib/netstandard2.0/Mono.TextTemplating.dll": {} + } + }, "MySqlConnector/2.2.5": { "type": "package", "compile": { @@ -854,6 +921,15 @@ "lib/netstandard1.1/System.Buffers.dll": {} } }, + "System.CodeDom/4.4.0": { + "type": "package", + "compile": { + "ref/netstandard2.0/System.CodeDom.dll": {} + }, + "runtime": { + "lib/netstandard2.0/System.CodeDom.dll": {} + } + }, "System.Collections/4.3.0": { "type": "package", "dependencies": { @@ -1715,6 +1791,43 @@ "ref/netstandard1.3/System.Text.Encoding.Extensions.dll": {} } }, + "System.Text.Encodings.Web/7.0.0": { + "type": "package", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "compile": { + "lib/net6.0/System.Text.Encodings.Web.dll": {} + }, + "runtime": { + "lib/net6.0/System.Text.Encodings.Web.dll": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + }, + "runtimeTargets": { + "runtimes/browser/lib/net6.0/System.Text.Encodings.Web.dll": { + "assetType": "runtime", + "rid": "browser" + } + } + }, + "System.Text.Json/7.0.0": { + "type": "package", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "7.0.0" + }, + "compile": { + "lib/net6.0/System.Text.Json.dll": {} + }, + "runtime": { + "lib/net6.0/System.Text.Json.dll": {} + }, + "build": { + "buildTransitive/net6.0/System.Text.Json.targets": {} + } + }, "System.Text.RegularExpressions/4.3.0": { "type": "package", "dependencies": { @@ -1923,7 +2036,10 @@ "dependencies": { "Haoliang.Core": "1.0.0", "Haoliang.Models": "1.0.0", - "Pomelo.EntityFrameworkCore.MySql": "6.0.32" + "Microsoft.EntityFrameworkCore": "7.0.2", + "Microsoft.EntityFrameworkCore.Design": "7.0.2", + "Microsoft.EntityFrameworkCore.Tools": "7.0.2", + "Pomelo.EntityFrameworkCore.MySql": "7.0.0" }, "compile": { "bin/placeholder/Haoliang.Data.dll": {} @@ -1996,6 +2112,24 @@ "coverlet.collector.nuspec" ] }, + "Humanizer.Core/2.14.1": { + "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "type": "package", + "path": "humanizer.core/2.14.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "humanizer.core.2.14.1.nupkg.sha512", + "humanizer.core.nuspec", + "lib/net6.0/Humanizer.dll", + "lib/net6.0/Humanizer.xml", + "lib/netstandard1.0/Humanizer.dll", + "lib/netstandard1.0/Humanizer.xml", + "lib/netstandard2.0/Humanizer.dll", + "lib/netstandard2.0/Humanizer.xml", + "logo.png" + ] + }, "Microsoft.CodeCoverage/16.11.0": { "sha512": "wf6lpAeCqP0KFfbDVtfL50lr7jY1gq0+0oSphyksfLOEygMDXqnaxHK5LPFtMEhYSEtgXdNyXNnEddOqQQUdlQ==", "type": "package", @@ -2164,6 +2298,21 @@ "microsoft.entityframeworkcore.analyzers.nuspec" ] }, + "Microsoft.EntityFrameworkCore.Design/7.0.2": { + "sha512": "gUUucCoJci8BBxOcU/XuuldUZpCo5Od8lwFEzZ5WywnvDfSmARnXNe97BpjL+JiBhSrnuTxW/wCJjWqPonXXHQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.design/7.0.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/net6.0/Microsoft.EntityFrameworkCore.Design.props", + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.dll", + "lib/net6.0/Microsoft.EntityFrameworkCore.Design.xml", + "microsoft.entityframeworkcore.design.7.0.2.nupkg.sha512", + "microsoft.entityframeworkcore.design.nuspec" + ] + }, "Microsoft.EntityFrameworkCore.InMemory/6.0.32": { "sha512": "F81tHD5mvTQO+vus49yPyIjWOcvADNSSwh9kq1dfZVNuxkkOPiF9fQWTMBvuqAS0C8l8XPBmWqdcyO4IgzVifA==", "type": "package", @@ -2192,6 +2341,31 @@ "microsoft.entityframeworkcore.relational.nuspec" ] }, + "Microsoft.EntityFrameworkCore.Tools/7.0.2": { + "sha512": "0Jx9feeGsUUlI+PEFkADyfQrGU+UIYh9N1I8ZO6X5bjYSKL2V1empkGTupvfrI7S9h4uA7aY8GQpjkCmIep7dg==", + "type": "package", + "path": "microsoft.entityframeworkcore.tools/7.0.2", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "lib/net6.0/_._", + "microsoft.entityframeworkcore.tools.7.0.2.nupkg.sha512", + "microsoft.entityframeworkcore.tools.nuspec", + "tools/EntityFrameworkCore.PS2.psd1", + "tools/EntityFrameworkCore.PS2.psm1", + "tools/EntityFrameworkCore.psd1", + "tools/EntityFrameworkCore.psm1", + "tools/about_EntityFrameworkCore.help.txt", + "tools/init.ps1", + "tools/net461/any/ef.exe", + "tools/net461/win-arm64/ef.exe", + "tools/net461/win-x86/ef.exe", + "tools/netcoreapp2.0/any/ef.dll", + "tools/netcoreapp2.0/any/ef.runtimeconfig.json" + ] + }, "Microsoft.Extensions.Caching.Abstractions/7.0.0": { "sha512": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", "type": "package", @@ -2331,6 +2505,34 @@ "useSharedDesignerContext.txt" ] }, + "Microsoft.Extensions.DependencyModel/7.0.0": { + "sha512": "oONNYd71J3LzkWc4fUHl3SvMfiQMYUCo/mDHDEu76hYYxdhdrPYv6fvGv9nnKVyhE9P0h20AU8RZB5OOWQcAXg==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net6.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net6.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net7.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net7.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.7.0.0.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, "Microsoft.Extensions.Logging/7.0.0": { "sha512": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "type": "package", @@ -3054,6 +3256,19 @@ "ref/xamarinwatchos10/_._" ] }, + "Mono.TextTemplating/2.2.1": { + "sha512": "KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==", + "type": "package", + "path": "mono.texttemplating/2.2.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net472/Mono.TextTemplating.dll", + "lib/netstandard2.0/Mono.TextTemplating.dll", + "mono.texttemplating.2.2.1.nupkg.sha512", + "mono.texttemplating.nuspec" + ] + }, "MySqlConnector/2.2.5": { "sha512": "6sinY78RvryhHwpup3awdjYO7d5hhWahb5p/1VDODJhSxJggV/sBbYuKK5IQF9TuzXABiddqUbmRfM884tqA3Q==", "type": "package", @@ -3449,6 +3664,27 @@ "system.buffers.nuspec" ] }, + "System.CodeDom/4.4.0": { + "sha512": "2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA==", + "type": "package", + "path": "system.codedom/4.4.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.dll", + "ref/net461/System.CodeDom.dll", + "ref/net461/System.CodeDom.xml", + "ref/netstandard2.0/System.CodeDom.dll", + "ref/netstandard2.0/System.CodeDom.xml", + "system.codedom.4.4.0.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, "System.Collections/4.3.0": { "sha512": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "type": "package", @@ -6096,6 +6332,108 @@ "system.text.encoding.extensions.nuspec" ] }, + "System.Text.Encodings.Web/7.0.0": { + "sha512": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==", + "type": "package", + "path": "system.text.encodings.web/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Text.Encodings.Web.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Text.Encodings.Web.targets", + "lib/net462/System.Text.Encodings.Web.dll", + "lib/net462/System.Text.Encodings.Web.xml", + "lib/net6.0/System.Text.Encodings.Web.dll", + "lib/net6.0/System.Text.Encodings.Web.xml", + "lib/net7.0/System.Text.Encodings.Web.dll", + "lib/net7.0/System.Text.Encodings.Web.xml", + "lib/netstandard2.0/System.Text.Encodings.Web.dll", + "lib/netstandard2.0/System.Text.Encodings.Web.xml", + "runtimes/browser/lib/net6.0/System.Text.Encodings.Web.dll", + "runtimes/browser/lib/net6.0/System.Text.Encodings.Web.xml", + "runtimes/browser/lib/net7.0/System.Text.Encodings.Web.dll", + "runtimes/browser/lib/net7.0/System.Text.Encodings.Web.xml", + "system.text.encodings.web.7.0.0.nupkg.sha512", + "system.text.encodings.web.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Text.Json/7.0.0": { + "sha512": "DaGSsVqKsn/ia6RG8frjwmJonfos0srquhw09TlT8KRw5I43E+4gs+/bZj4K0vShJ5H9imCuXupb4RmS+dBy3w==", + "type": "package", + "path": "system.text.json/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "buildTransitive/net461/System.Text.Json.targets", + "buildTransitive/net462/System.Text.Json.targets", + "buildTransitive/net6.0/System.Text.Json.targets", + "buildTransitive/netcoreapp2.0/System.Text.Json.targets", + "buildTransitive/netstandard2.0/System.Text.Json.targets", + "lib/net462/System.Text.Json.dll", + "lib/net462/System.Text.Json.xml", + "lib/net6.0/System.Text.Json.dll", + "lib/net6.0/System.Text.Json.xml", + "lib/net7.0/System.Text.Json.dll", + "lib/net7.0/System.Text.Json.xml", + "lib/netstandard2.0/System.Text.Json.dll", + "lib/netstandard2.0/System.Text.Json.xml", + "system.text.json.7.0.0.nupkg.sha512", + "system.text.json.nuspec", + "useSharedDesignerContext.txt" + ] + }, "System.Text.RegularExpressions/4.3.0": { "sha512": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "type": "package", @@ -6786,17 +7124,5 @@ "runtimeIdentifierGraphPath": "/usr/lib/dotnet/sdk/6.0.136/RuntimeIdentifierGraph.json" } } - }, - "logs": [ - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Data depends on Pomelo.EntityFrameworkCore.MySql (>= 6.0.32) but Pomelo.EntityFrameworkCore.MySql 6.0.32 was not found. An approximate best match of Pomelo.EntityFrameworkCore.MySql 7.0.0 was resolved.", - "libraryId": "Pomelo.EntityFrameworkCore.MySql", - "targetGraphs": [ - "net6.0" - ] - } - ] + } } \ No newline at end of file diff --git a/Haoliang.Tests/obj/project.nuget.cache b/Haoliang.Tests/obj/project.nuget.cache index 24a2adc..61066c1 100644 --- a/Haoliang.Tests/obj/project.nuget.cache +++ b/Haoliang.Tests/obj/project.nuget.cache @@ -1,22 +1,26 @@ { "version": 2, - "dgSpecHash": "ydLeO0eAP1jTTT1do6Foo3BuUoiljVixZH+diF1n3qZ54+Dyyyv2Ysh90kKku2kx3Ai/UVRyYfqpUfFcrfjVRA==", + "dgSpecHash": "vhR3s37eZ0JKCSsDdknFHWgEI3WxKpEWEco7km6fI//ih0qiSnXd8nDqT/ZNGzLXv/gF/3itOvSLKFhkCmlPIw==", "success": true, "projectFilePath": "/root/opencode/haoliang/Haoliang.Tests/Haoliang.Tests.csproj", "expectedPackageFiles": [ "/root/.nuget/packages/coverlet.collector/3.1.0/coverlet.collector.3.1.0.nupkg.sha512", + "/root/.nuget/packages/humanizer.core/2.14.1/humanizer.core.2.14.1.nupkg.sha512", "/root/.nuget/packages/microsoft.codecoverage/16.11.0/microsoft.codecoverage.16.11.0.nupkg.sha512", "/root/.nuget/packages/microsoft.csharp/4.0.1/microsoft.csharp.4.0.1.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.inmemory/6.0.32/microsoft.entityframeworkcore.inmemory.6.0.32.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.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.options/7.0.0/microsoft.extensions.options.7.0.0.nupkg.sha512", @@ -27,6 +31,7 @@ "/root/.nuget/packages/microsoft.testplatform.objectmodel/16.11.0/microsoft.testplatform.objectmodel.16.11.0.nupkg.sha512", "/root/.nuget/packages/microsoft.testplatform.testhost/16.11.0/microsoft.testplatform.testhost.16.11.0.nupkg.sha512", "/root/.nuget/packages/microsoft.win32.primitives/4.3.0/microsoft.win32.primitives.4.3.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/netstandard.library/1.6.1/netstandard.library.1.6.1.nupkg.sha512", "/root/.nuget/packages/newtonsoft.json/9.0.1/newtonsoft.json.9.0.1.nupkg.sha512", @@ -50,6 +55,7 @@ "/root/.nuget/packages/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512", "/root/.nuget/packages/system.appcontext/4.3.0/system.appcontext.4.3.0.nupkg.sha512", "/root/.nuget/packages/system.buffers/4.3.0/system.buffers.4.3.0.nupkg.sha512", + "/root/.nuget/packages/system.codedom/4.4.0/system.codedom.4.4.0.nupkg.sha512", "/root/.nuget/packages/system.collections/4.3.0/system.collections.4.3.0.nupkg.sha512", "/root/.nuget/packages/system.collections.concurrent/4.3.0/system.collections.concurrent.4.3.0.nupkg.sha512", "/root/.nuget/packages/system.console/4.3.0/system.console.4.3.0.nupkg.sha512", @@ -98,6 +104,8 @@ "/root/.nuget/packages/system.security.cryptography.x509certificates/4.3.0/system.security.cryptography.x509certificates.4.3.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.encoding.extensions/4.3.0/system.text.encoding.extensions.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.text.regularexpressions/4.3.0/system.text.regularexpressions.4.3.0.nupkg.sha512", "/root/.nuget/packages/system.threading/4.3.0/system.threading.4.3.0.nupkg.sha512", "/root/.nuget/packages/system.threading.tasks/4.3.0/system.threading.tasks.4.3.0.nupkg.sha512", @@ -114,16 +122,5 @@ "/root/.nuget/packages/xunit.extensibility.execution/2.4.1/xunit.extensibility.execution.2.4.1.nupkg.sha512", "/root/.nuget/packages/xunit.runner.visualstudio/2.4.3/xunit.runner.visualstudio.2.4.3.nupkg.sha512" ], - "logs": [ - { - "code": "NU1603", - "level": "Warning", - "warningLevel": 1, - "message": "Haoliang.Data depends on Pomelo.EntityFrameworkCore.MySql (>= 6.0.32) but Pomelo.EntityFrameworkCore.MySql 6.0.32 was not found. An approximate best match of Pomelo.EntityFrameworkCore.MySql 7.0.0 was resolved.", - "libraryId": "Pomelo.EntityFrameworkCore.MySql", - "targetGraphs": [ - "net6.0" - ] - } - ] + "logs": [] } \ No newline at end of file diff --git a/Haoliang/Data/DataSeeder.cs b/Haoliang/Data/DataSeeder.cs new file mode 100644 index 0000000..7647513 --- /dev/null +++ b/Haoliang/Data/DataSeeder.cs @@ -0,0 +1,655 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Haoliang.Data.Entities; +using Haoliang.Models.System; +using Haoliang.Models.User; + +namespace Haoliang.Data +{ + public static class DataSeeder + { + public static async Task SeedDataAsync(IServiceProvider serviceProvider) + { + using (var scope = serviceProvider.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + var logger = scope.ServiceProvider.GetService>(); + + try + { + logger.LogInformation("Starting database seed process..."); + + // Apply any pending migrations + await context.Database.MigrateAsync(); + + // Seed roles + await SeedRolesAsync(context); + + // Seed users + await SeedUsersAsync(context); + + // Seed permissions + await SeedPermissionsAsync(context); + + // seed system configurations + await SeedSystemConfigsAsync(context); + + // seed alarm rules + await SeedAlarmRulesAsync(context); + + // seed device templates + await SeedDeviceTemplatesAsync(context); + + logger.LogInformation("Database seed process completed successfully."); + } + catch (Exception ex) + { + logger.LogError(ex, "Error occurred during database seed process."); + throw; + } + } + } + + private static async Task SeedRolesAsync(CNCBusinessDbContext context) + { + if (!await context.Roles.AnyAsync()) + { + var roles = new List + { + new Role + { + RoleName = "Administrator", + Description = "System administrators with full access", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new Role + { + RoleName = "Manager", + Description = "Department managers with access to reporting and device management", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new Role + { + RoleName = "Operator", + Description = "Device operators with access to daily operations", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new Role + { + RoleName = "Viewer", + Description = "Read-only access to system data", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + } + }; + + await context.Roles.AddRangeAsync(roles); + await context.SaveChangesAsync(); + } + } + + private static async Task SeedUsersAsync(CNCBusinessDbContext context) + { + if (!await context.Users.AnyAsync()) + { + // Create default admin user + var adminRole = await context.Roles.FirstOrDefaultAsync(r => r.RoleName == "Administrator"); + if (adminRole != null) + { + var adminUser = new User + { + Username = "admin", + Email = "admin@cncsystem.com", + PasswordHash = BCrypt.Net.BCrypt.HashPassword("Admin@123"), + FirstName = "System", + LastName = "Administrator", + RoleId = adminRole.RoleId, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + + await context.Users.AddAsync(adminUser); + await context.SaveChangesAsync(); + } + + // Create default manager user + var managerRole = await context.Roles.FirstOrDefaultAsync(r => r.RoleName == "Manager"); + if (managerRole != null) + { + var managerUser = new User + { + Username = "manager", + Email = "manager@cncsystem.com", + PasswordHash = BCrypt.Net.BCrypt.HashPassword("Manager@123"), + FirstName = "John", + LastName = "Doe", + Department = "Production", + RoleId = managerRole.RoleId, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + + await context.Users.AddAsync(managerUser); + await context.SaveChangesAsync(); + } + + // Create default operator user + var operatorRole = await context.Roles.FirstOrDefaultAsync(r => r.RoleName == "Operator"); + if (operatorRole != null) + { + var operatorUser = new User + { + Username = "operator", + Email = "operator@cncsystem.com", + PasswordHash = BCrypt.Net.BCrypt.HashPassword("Operator@123"), + FirstName = "Jane", + LastName = "Smith", + Department = "Production", + RoleId = operatorRole.RoleId, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + + await context.Users.AddAsync(operatorUser); + await context.SaveChangesAsync(); + } + } + } + + private static async Task SeedPermissionsAsync(CNCBusinessDbContext context) + { + if (!await context.Permissions.AnyAsync()) + { + var permissions = new List + { + // Device Management + new Permission { PermissionName = "devices.create", Description = "Create new devices", Category = "Device" }, + new Permission { PermissionName = "devices.read", Description = "View device information", Category = "Device" }, + new Permission { PermissionName = "devices.update", Description = "Update device information", Category = "Device" }, + new Permission { PermissionName = "devices.delete", Description = "Delete devices", Category = "Device" }, + new Permission { PermissionName = "devices.control", Description = "Control device operations", Category = "Device" }, + + // Production Management + new Permission { PermissionName = "production.create", Description = "Create production records", Category = "Production" }, + new Permission { PermissionName = "production.read", Description = "View production data", Category = "Production" }, + new Permission { PermissionName = "production.update", Description = "Update production records", Category = "Production" }, + new Permission { PermissionName = "production.delete", Description = "Delete production records", Category = "Production" }, + new Permission { PermissionName = "production.analyze", Description = "Analyze production data", Category = "Production" }, + + // Alarm Management + new Permission { PermissionName = "alarms.create", Description = "Create alarms", Category = "Alarm" }, + new Permission { PermissionName = "alarms.read", Description = "View alarms", Category = "Alarm" }, + new Permission { PermissionName = "alarms.update", Description = "Update alarms", Category = "Alarm" }, + new Permission { PermissionName = "alarms.delete", Description = "Delete alarms", Category = "Alarm" }, + new Permission { PermissionName = "alarms.resolve", Description = "Resolve alarms", Category = "Alarm" }, + + // Template Management + new Permission { PermissionName = "templates.create", Description = "Create templates", Category = "Template" }, + new Permission { PermissionName = "templates.read", Description = "View templates", Category = "Template" }, + new Permission { PermissionName = "templates.update", Description = "Update templates", Category = "Template" }, + new Permission { PermissionName = "templates.delete", Description = "Delete templates", Category = "Template" }, + + // System Management + new Permission { PermissionName = "system.config", Description = "Manage system configuration", Category = "System" }, + new Permission { PermissionName = "system.logs", Description = "View system logs", Category = "System" }, + new Permission { PermissionName = "system.users", Description = "Manage users", Category = "System" }, + new Permission { PermissionName = "system.roles", Description = "Manage roles", Category = "System" }, + new Permission { PermissionName = "system.backup", Description = "System backup operations", Category = "System" }, + + // Real-time Monitoring + new Permission { PermissionName = "monitoring.live", Description = "View real-time monitoring", Category = "Monitoring" }, + new Permission { PermissionName = "monitoring.history", Description = "View monitoring history", Category = "Monitoring" }, + new Permission { PermissionName = "monitoring.export", Description = "Export monitoring data", Category = "Monitoring" }, + + // Reports + new Permission { PermissionName = "reports.generate", Description = "Generate reports", Category = "Reporting" }, + new Permission { PermissionName = "reports.view", Description = "View reports", Category = "Reporting" }, + new Permission { PermissionName = "reports.export", Description = "Export reports", Category = "Reporting" }, + + // API Access + new Permission { PermissionName = "api.access", Description = "Access API endpoints", Category = "API" }, + new Permission { PermissionName = "api.admin", Description = "Admin API access", Category = "API" } + }; + + await context.Permissions.AddRangeAsync(permissions); + await context.SaveChangesAsync(); + } + } + + private static async Task SeedSystemConfigsAsync(CNCBusinessDbContext context) + { + if (!await context.SystemConfigs.AnyAsync()) + { + var configs = new List + { + // Application Settings + new SystemConfig + { + ConfigKey = "app.name", + ConfigValue = "CNC Machine Monitoring System", + Description = "Application name", + Category = "Application", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "app.version", + ConfigValue = "1.0.0", + Description = "Application version", + Category = "Application", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + + // Collection Settings + new SystemConfig + { + ConfigKey = "collection.interval", + ConfigValue = "30", + Description = "Default collection interval in seconds", + Category = "Collection", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "collection.timeout", + ConfigValue = "10", + Description = "Collection timeout in seconds", + Category = "Collection", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "collection.retry.attempts", + ConfigValue = "3", + Description = "Number of retry attempts for failed collections", + Category = "Collection", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + + // Alert Settings + new SystemConfig + { + ConfigKey = "alert.email.enabled", + ConfigValue = "true", + Description = "Enable email alerts", + Category = "Alert", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "alert.sms.enabled", + ConfigValue = "false", + Description = "Enable SMS alerts", + Category = "Alert", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "alert.wechat.enabled", + ConfigValue = "false", + Description = "Enable WeChat alerts", + Category = "Alert", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + + // Data Retention + new SystemConfig + { + ConfigKey = "data.retention.days", + ConfigValue = "90", + Description = "Data retention period in days", + Category = "Data", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "log.retention.days", + ConfigValue = "30", + Description = "Log retention period in days", + Category = "Data", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + + // Authentication + new SystemConfig + { + ConfigKey = "auth.session.timeout", + ConfigValue = "60", + Description = "Session timeout in minutes", + Category = "Authentication", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "auth.max.login.attempts", + ConfigValue = "5", + Description = "Maximum login attempts before lockout", + Category = "Authentication", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + + // Performance + new SystemConfig + { + ConfigKey = "performance.cache.enabled", + ConfigValue = "true", + Description = "Enable caching", + Category = "Performance", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new SystemConfig + { + ConfigKey = "performance.cache.duration", + ConfigValue = "10", + Description = "Cache duration in minutes", + Category = "Performance", + IsActive = true, + IsDefault = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + } + }; + + await context.SystemConfigs.AddRangeAsync(configs); + await context.SaveChangesAsync(); + } + } + + private static async Task SeedAlarmRulesAsync(CNCBusinessDbContext context) + { + if (!await context.AlarmRules.AnyAsync()) + { + var alarmRules = new List + { + new AlarmRule + { + RuleName = "Device Offline", + DeviceId = null, // Apply to all devices + AlarmType = "DeviceOffline", + Condition = "device_offline", + Threshold = "", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new AlarmRule + { + RuleName = "High Temperature", + DeviceId = null, // Apply to all devices + AlarmType = "DeviceError", + Condition = "temperature > 100", + Threshold = "100", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new AlarmRule + { + RuleName = "Low Production Rate", + DeviceId = null, // Apply to all devices + AlarmType = "ProductionError", + Condition = "production_rate < 10", + Threshold = "10", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new AlarmRule + { + RuleName = "System Error", + DeviceId = null, // Apply to all devices + AlarmType = "SystemError", + Condition = "system_error > 0", + Threshold = "0", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + } + }; + + await context.AlarmRules.AddRangeAsync(alarmRules); + await context.SaveChangesAsync(); + } + } + + private static async Task SeedDeviceTemplatesAsync(CNCBusinessDbContext context) + { + if (!await context.CNCTemplates.AnyAsync()) + { + var templates = new List + { + new CNCBrandTemplate + { + TemplateName = "Fanuc Standard", + BrandName = "Fanuc", + Version = "1.0", + Description = "Standard template for Fanuc CNC machines", + TagsJson = @"[ + { + ""systemTagId"": ""_io_status"", + ""deviceTagId"": ""_io_status"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Input/Output status"" + }, + { + ""systemTagId"": ""Tag5"", + ""deviceTagId"": ""Tag5"", + ""dataType"": ""string"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""NC program name"" + }, + { + ""systemTagId"": ""Tag8"", + ""deviceTagId"": ""Tag8"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Cumulative count"" + }, + { + ""systemTagId"": ""Tag9"", + ""deviceTagId"": ""Tag9"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Run status"" + }, + { + ""systemTagId"": ""Tag11"", + ""deviceTagId"": ""Tag11"", + ""dataType"": ""string"", + ""isRequired"": false, + ""validationRegex"": """", + ""description"": ""Operating mode"" + }, + { + ""systemTagId"": ""Tag14"", + ""deviceTagId"": ""Tag14"", + ""dataType"": ""int"", + ""isRequired"": false, + ""validationRegex"": """", + ""description"": ""Spindle override"" + } + ]", + DataProcessingRulesJson = @"[ + { + ""ruleName"": ""Production Calculation"", + ""condition"": ""Tag9 == 1 && Tag8 > 0"", + ""action"": ""calculate_production"" + }, + { + ""ruleName"": ""Status Update"", + ""condition"": ""_io_status == 1"", + ""action"": ""set_running"" + } + ]", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new CNCBrandTemplate + { + TemplateName = "Mitsubishi Standard", + BrandName = "Mitsubishi", + Version = "1.0", + Description = "Standard template for Mitsubishi CNC machines", + TagsJson = @"[ + { + ""systemTagId"": ""_io_status"", + ""deviceTagId"": ""_io_status"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Input/Output status"" + }, + { + ""systemTagId"": ""ProgramName"", + ""deviceTagId"": ""ProgramName"", + ""dataType"": ""string"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""NC program name"" + }, + { + ""systemTagId"": ""TotalCount"", + ""deviceTagId"": ""TotalCount"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Total production count"" + }, + { + ""systemTagId"": ""RunStatus"", + ""deviceTagId"": ""RunStatus"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Running status"" + } + ]", + DataProcessingRulesJson = @"[ + { + ""ruleName"": ""Production Calculation"", + ""condition"": ""RunStatus == 1 && TotalCount > 0"", + ""action"": ""calculate_production"" + } + ]", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }, + new CNCBrandTemplate + { + TemplateName = "Siemens Standard", + BrandName = "Siemens", + Version = "1.0", + Description = "Standard template for Siemens CNC machines", + TagsJson = @"[ + { + ""systemTagId"": ""ChannelState"", + ""deviceTagId"": ""ChannelState"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Channel state"" + }, + { + ""systemTagId"": ""ProgramName"", + ""deviceTagId"": ""ProgramName"", + ""dataType"": ""string"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Program name"" + }, + { + ""systemTagId"": ""PartCount"", + ""deviceTagId"": ""PartCount"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Part count"" + }, + { + ""systemTagId"": ""MachineState"", + ""deviceTagId"": ""MachineState"", + ""dataType"": ""int"", + ""isRequired"": true, + ""validationRegex"": """", + ""description"": ""Machine state"" + } + ]", + DataProcessingRulesJson = @"[ + { + ""ruleName"": ""Production Calculation"", + ""condition"": ""MachineState == 4 && PartCount > 0"", + ""action"": ""calculate_production"" + } + ]", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + } + }; + + await context.CNCTemplates.AddRangeAsync(templates); + await context.SaveChangesAsync(); + } + } + } +} \ No newline at end of file