using Microsoft.AspNetCore.Mvc; using WIDESEAWCS_S7Simulator.Application.Protocol; using WIDESEAWCS_S7Simulator.Core.Entities; using WIDESEAWCS_S7Simulator.Core.Interfaces; namespace WIDESEAWCS_S7Simulator.Server.Controllers { /// /// 仿真器实例管理控制器 /// [ApiController] [Route("api/[controller]")] public class SimulatorInstancesController : ControllerBase { private readonly ISimulatorInstanceManager _instanceManager; private readonly IProtocolTemplateService _protocolTemplateService; private readonly ILogger _logger; public SimulatorInstancesController( ISimulatorInstanceManager instanceManager, IProtocolTemplateService protocolTemplateService, ILogger logger) { _instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); _protocolTemplateService = protocolTemplateService ?? throw new ArgumentNullException(nameof(protocolTemplateService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// 获取所有实例列表 /// [HttpGet("GetAll")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public ActionResult GetAllInstances() { try { var instances = _instanceManager.GetAllInstances(); var result = instances.Select(i => new { instanceId = i.Config.Id, name = i.Config.Name, plcType = i.Config.PLCType.ToString(), port = i.Config.Port, status = i.GetState().Status.ToString(), clientCount = i.GetState().ClientCount, totalRequests = i.GetState().TotalRequests, startTime = i.GetState().StartTime, lastActivityTime = i.GetState().LastActivityTime, errorMessage = i.GetState().ErrorMessage }).ToList(); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Failed to get all instances"); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to retrieve instances" }); } } /// /// 创建新实例 /// [HttpPost("Create")] [ProducesResponseType(typeof(object), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateInstance([FromBody] InstanceConfig config) { try { if (config == null) { return BadRequest(new { error = "Instance configuration is required" }); } if (string.IsNullOrWhiteSpace(config.Id)) { return BadRequest(new { error = "Instance ID is required" }); } if (string.IsNullOrWhiteSpace(config.Name)) { return BadRequest(new { error = "Instance name is required" }); } if (_instanceManager.InstanceExists(config.Id)) { return Conflict(new { error = $"Instance with ID '{config.Id}' already exists" }); } if (config.Port <= 0 || config.Port > 65535) { return BadRequest(new { error = "Port must be between 1 and 65535" }); } NormalizeMemoryConfig(config); if (string.IsNullOrWhiteSpace(config.ProtocolTemplateId)) { return BadRequest(new { error = "Protocol template is required" }); } if (!await _protocolTemplateService.ExistsAsync(config.ProtocolTemplateId)) { return BadRequest(new { error = $"Protocol template '{config.ProtocolTemplateId}' not found" }); } var instance = await _instanceManager.CreateInstanceAsync(config); var state = instance.GetState(); var result = new { instanceId = instance.Config.Id, name = instance.Config.Name, plcType = instance.Config.PLCType.ToString(), port = instance.Config.Port, status = state.Status.ToString(), clientCount = state.ClientCount, totalRequests = state.TotalRequests, startTime = state.StartTime, lastActivityTime = state.LastActivityTime, errorMessage = state.ErrorMessage }; return CreatedAtAction( nameof(GetInstance), new { id = config.Id }, result); } catch (ArgumentException ex) { _logger.LogWarning(ex, "Invalid instance configuration"); return BadRequest(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Failed to create instance"); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to create instance" }); } } /// /// 获取指定实例详情 /// [HttpGet("GetInstance")] [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetInstance(string id) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } var instance = _instanceManager.GetInstance(id); if (instance == null) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } var state = instance.GetState(); var result = new { instanceId = instance.Config.Id, name = instance.Config.Name, plcType = instance.Config.PLCType.ToString(), port = instance.Config.Port, status = state.Status.ToString(), clientCount = state.ClientCount, totalRequests = state.TotalRequests, startTime = state.StartTime, lastActivityTime = state.LastActivityTime, errorMessage = state.ErrorMessage }; return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Failed to get instance {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to retrieve instance" }); } } /// /// 获取指定实例的配置 /// [HttpGet("GetInstanceConfig")] [ProducesResponseType(typeof(InstanceConfig), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetInstanceConfig(string id) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } var instance = _instanceManager.GetInstance(id); if (instance == null) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } return Ok(instance.Config); } catch (Exception ex) { _logger.LogError(ex, "Failed to get instance config {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to retrieve instance config" }); } } /// /// 更新实例配置 /// [HttpPut("Update")] [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdateInstance([FromQuery] string id, [FromBody] InstanceConfig config) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } if (config == null) { return BadRequest(new { error = "Instance configuration is required" }); } var instance = _instanceManager.GetInstance(id); if (instance == null) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } // Validate port if changed if (config.Port <= 0 || config.Port > 65535) { return BadRequest(new { error = "Port must be between 1 and 65535" }); } NormalizeMemoryConfig(config); if (string.IsNullOrWhiteSpace(config.ProtocolTemplateId)) { return BadRequest(new { error = "Protocol template is required" }); } if (!await _protocolTemplateService.ExistsAsync(config.ProtocolTemplateId)) { return BadRequest(new { error = $"Protocol template '{config.ProtocolTemplateId}' not found" }); } // Delete existing instance and recreate with new config // Note: This is a simplified approach. In production, you might want to support hot-reload bool wasRunning = instance.GetState().Status == Core.Enums.InstanceStatus.Running; await _instanceManager.DeleteInstanceAsync(id, deleteConfig: false); config.Id = id; // Ensure ID matches route parameter var newInstance = await _instanceManager.CreateInstanceAsync(config); if (wasRunning) { await _instanceManager.StartInstanceAsync(id); } var state = newInstance.GetState(); var result = new { instanceId = newInstance.Config.Id, name = newInstance.Config.Name, plcType = newInstance.Config.PLCType.ToString(), port = newInstance.Config.Port, status = state.Status.ToString(), clientCount = state.ClientCount, totalRequests = state.TotalRequests, startTime = state.StartTime, lastActivityTime = state.LastActivityTime, errorMessage = state.ErrorMessage }; return Ok(result); } catch (ArgumentException ex) { _logger.LogWarning(ex, "Invalid instance configuration"); return BadRequest(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Failed to update instance {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to update instance" }); } } /// /// 删除实例 /// [HttpDelete("Delete")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteInstance(string id, bool deleteConfig = true) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } if (!_instanceManager.InstanceExists(id)) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } await _instanceManager.DeleteInstanceAsync(id, deleteConfig); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Failed to delete instance {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to delete instance" }); } } /// /// 启动实例 /// [HttpPost("start")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task StartInstance([FromQuery] string id) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } if (!_instanceManager.InstanceExists(id)) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } bool success = await _instanceManager.StartInstanceAsync(id); if (!success) { return BadRequest(new { error = "Failed to start instance" }); } var instance = _instanceManager.GetInstance(id); var state = instance?.GetState(); var result = new { instanceId = instance.Config.Id, name = instance.Config.Name, plcType = instance.Config.PLCType.ToString(), port = instance.Config.Port, status = state.Status.ToString(), clientCount = state.ClientCount, totalRequests = state.TotalRequests, startTime = state.StartTime, lastActivityTime = state.LastActivityTime, errorMessage = state.ErrorMessage }; return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Failed to start instance {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to start instance" }); } } /// /// 停止实例 /// [HttpPost("stop")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task StopInstance([FromQuery] string id) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } if (!_instanceManager.InstanceExists(id)) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } await _instanceManager.StopInstanceAsync(id); var instance = _instanceManager.GetInstance(id); var state = instance?.GetState(); var result = new { instanceId = instance.Config.Id, name = instance.Config.Name, plcType = instance.Config.PLCType.ToString(), port = instance.Config.Port, status = state.Status.ToString(), clientCount = state.ClientCount, totalRequests = state.TotalRequests, startTime = state.StartTime, lastActivityTime = state.LastActivityTime, errorMessage = state.ErrorMessage }; return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Failed to stop instance {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to stop instance" }); } } /// /// 重启实例 /// [HttpPost("restart")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task RestartInstance([FromQuery] string id) { try { if (string.IsNullOrWhiteSpace(id)) { return BadRequest(new { error = "Instance ID is required" }); } if (!_instanceManager.InstanceExists(id)) { return NotFound(new { error = $"Instance with ID '{id}' not found" }); } bool success = await _instanceManager.RestartInstanceAsync(id); if (!success) { return BadRequest(new { error = "Failed to restart instance" }); } var instance = _instanceManager.GetInstance(id); var state = instance?.GetState(); var result = new { instanceId = instance.Config.Id, name = instance.Config.Name, plcType = instance.Config.PLCType.ToString(), port = instance.Config.Port, status = state.Status.ToString(), clientCount = state.ClientCount, totalRequests = state.TotalRequests, startTime = state.StartTime, lastActivityTime = state.LastActivityTime, errorMessage = state.ErrorMessage }; return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Failed to restart instance {InstanceId}", id); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to restart instance" }); } } /// /// 启动所有自动启动实例 /// [HttpPost("start-all")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task StartAllAutoStartInstances() { try { await _instanceManager.LoadSavedInstancesAsync(autoStart: true); return Ok(new { message = "Started all auto-start instances", runningCount = _instanceManager.GetRunningInstanceCount() }); } catch (Exception ex) { _logger.LogError(ex, "Failed to start all instances"); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to start instances" }); } } /// /// 停止所有实例 /// [HttpPost("stop-all")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task StopAllInstances() { try { await _instanceManager.StopAllInstancesAsync(); return Ok(new { message = "Stopped all instances", runningCount = _instanceManager.GetRunningInstanceCount() }); } catch (Exception ex) { _logger.LogError(ex, "Failed to stop all instances"); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to stop instances" }); } } /// /// 规范化并校验内存配置,优先使用显式 DB 块列表。 /// private static void NormalizeMemoryConfig(InstanceConfig config) { if (config.MemoryConfig == null) { config.MemoryConfig = new MemoryRegionConfig(); } var dbBlockNumbers = (config.MemoryConfig.DBBlockNumbers ?? new List()) .Where(x => x > 0) .Distinct() .OrderBy(x => x) .ToList(); if (dbBlockNumbers.Count == 0) { var count = config.MemoryConfig.DBBlockCount > 0 ? config.MemoryConfig.DBBlockCount : 1; dbBlockNumbers = Enumerable.Range(1, count).ToList(); } config.MemoryConfig.DBBlockNumbers = dbBlockNumbers; config.MemoryConfig.DBBlockCount = dbBlockNumbers.Count; config.MemoryConfig.DBBlockSize = config.MemoryConfig.DBBlockSize > 0 ? config.MemoryConfig.DBBlockSize : 1024; } } }