| | |
| | | using Microsoft.AspNetCore.Mvc; |
| | | using WIDESEAWCS_S7Simulator.Application.Protocol; |
| | | using WIDESEAWCS_S7Simulator.Core.Entities; |
| | | using WIDESEAWCS_S7Simulator.Core.Interfaces; |
| | | |
| | |
| | | public class SimulatorInstancesController : ControllerBase |
| | | { |
| | | private readonly ISimulatorInstanceManager _instanceManager; |
| | | private readonly IProtocolTemplateService _protocolTemplateService; |
| | | private readonly ILogger<SimulatorInstancesController> _logger; |
| | | |
| | | public SimulatorInstancesController( |
| | | ISimulatorInstanceManager instanceManager, |
| | | IProtocolTemplateService protocolTemplateService, |
| | | ILogger<SimulatorInstancesController> logger) |
| | | { |
| | | _instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); |
| | | _protocolTemplateService = protocolTemplateService ?? throw new ArgumentNullException(nameof(protocolTemplateService)); |
| | | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取所有实例列表 |
| | | /// </summary> |
| | | [HttpGet] |
| | | [HttpGet("GetAll")] |
| | | [ProducesResponseType(typeof(IEnumerable<object>), StatusCodes.Status200OK)] |
| | | public ActionResult GetAllInstances() |
| | | { |
| | |
| | | /// <summary> |
| | | /// 创建新实例 |
| | | /// </summary> |
| | | [HttpPost] |
| | | [ProducesResponseType(typeof(InstanceState), StatusCodes.Status201Created)] |
| | | [HttpPost("Create")] |
| | | [ProducesResponseType(typeof(object), StatusCodes.Status201Created)] |
| | | [ProducesResponseType(StatusCodes.Status400BadRequest)] |
| | | public async Task<ActionResult<InstanceState>> CreateInstance([FromBody] InstanceConfig config) |
| | | public async Task<ActionResult> CreateInstance([FromBody] InstanceConfig config) |
| | | { |
| | | try |
| | | { |
| | |
| | | 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 }, |
| | | state); |
| | | result); |
| | | } |
| | | catch (ArgumentException ex) |
| | | { |
| | |
| | | /// <summary> |
| | | /// 获取指定实例详情 |
| | | /// </summary> |
| | | [HttpGet("{id}")] |
| | | [ProducesResponseType(typeof(InstanceState), StatusCodes.Status200OK)] |
| | | [HttpGet("GetInstance")] |
| | | [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | public ActionResult<InstanceState> GetInstance(string id) |
| | | public ActionResult GetInstance(string id) |
| | | { |
| | | try |
| | | { |
| | |
| | | } |
| | | |
| | | var state = instance.GetState(); |
| | | return Ok(state); |
| | | 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) |
| | | { |
| | |
| | | /// <summary> |
| | | /// 获取指定实例的配置 |
| | | /// </summary> |
| | | [HttpGet("{id}/config")] |
| | | [HttpGet("GetInstanceConfig")] |
| | | [ProducesResponseType(typeof(InstanceConfig), StatusCodes.Status200OK)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | public ActionResult<InstanceConfig> GetInstanceConfig(string id) |
| | |
| | | /// <summary> |
| | | /// 更新实例配置 |
| | | /// </summary> |
| | | [HttpPut("{id}")] |
| | | [ProducesResponseType(typeof(InstanceState), StatusCodes.Status200OK)] |
| | | [HttpPut("Update")] |
| | | [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] |
| | | [ProducesResponseType(StatusCodes.Status400BadRequest)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | public async Task<ActionResult<InstanceState>> UpdateInstance(string id, [FromBody] InstanceConfig config) |
| | | public async Task<ActionResult> UpdateInstance([FromQuery] string id, [FromBody] InstanceConfig config) |
| | | { |
| | | try |
| | | { |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | var state = newInstance.GetState(); |
| | | return Ok(state); |
| | | 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) |
| | | { |
| | |
| | | /// <summary> |
| | | /// 删除实例 |
| | | /// </summary> |
| | | [HttpDelete("{id}")] |
| | | [HttpDelete("Delete")] |
| | | [ProducesResponseType(StatusCodes.Status204NoContent)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | public async Task<IActionResult> DeleteInstance(string id, [FromQuery] bool deleteConfig = true) |
| | | public async Task<IActionResult> DeleteInstance(string id, bool deleteConfig = true) |
| | | { |
| | | try |
| | | { |
| | |
| | | /// <summary> |
| | | /// 启动实例 |
| | | /// </summary> |
| | | [HttpPost("{id}/start")] |
| | | [HttpPost("start")] |
| | | [ProducesResponseType(StatusCodes.Status200OK)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | [ProducesResponseType(StatusCodes.Status400BadRequest)] |
| | | public async Task<ActionResult<InstanceState>> StartInstance(string id) |
| | | public async Task<ActionResult> StartInstance([FromQuery] string id) |
| | | { |
| | | try |
| | | { |
| | |
| | | |
| | | var instance = _instanceManager.GetInstance(id); |
| | | var state = instance?.GetState(); |
| | | return Ok(state); |
| | | 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) |
| | | { |
| | |
| | | /// <summary> |
| | | /// 停止实例 |
| | | /// </summary> |
| | | [HttpPost("{id}/stop")] |
| | | [HttpPost("stop")] |
| | | [ProducesResponseType(StatusCodes.Status200OK)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | public async Task<ActionResult<InstanceState>> StopInstance(string id) |
| | | public async Task<ActionResult> StopInstance([FromQuery] string id) |
| | | { |
| | | try |
| | | { |
| | |
| | | |
| | | var instance = _instanceManager.GetInstance(id); |
| | | var state = instance?.GetState(); |
| | | return Ok(state); |
| | | 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) |
| | | { |
| | |
| | | /// <summary> |
| | | /// 重启实例 |
| | | /// </summary> |
| | | [HttpPost("{id}/restart")] |
| | | [HttpPost("restart")] |
| | | [ProducesResponseType(StatusCodes.Status200OK)] |
| | | [ProducesResponseType(StatusCodes.Status404NotFound)] |
| | | [ProducesResponseType(StatusCodes.Status400BadRequest)] |
| | | public async Task<ActionResult<InstanceState>> RestartInstance(string id) |
| | | public async Task<ActionResult> RestartInstance([FromQuery] string id) |
| | | { |
| | | try |
| | | { |
| | |
| | | |
| | | var instance = _instanceManager.GetInstance(id); |
| | | var state = instance?.GetState(); |
| | | return Ok(state); |
| | | 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) |
| | | { |
| | |
| | | return StatusCode(StatusCodes.Status500InternalServerError, new { error = "Failed to stop instances" }); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 规范化并校验内存配置,优先使用显式 DB 块列表。 |
| | | /// </summary> |
| | | private static void NormalizeMemoryConfig(InstanceConfig config) |
| | | { |
| | | if (config.MemoryConfig == null) |
| | | { |
| | | config.MemoryConfig = new MemoryRegionConfig(); |
| | | } |
| | | |
| | | var dbBlockNumbers = (config.MemoryConfig.DBBlockNumbers ?? new List<int>()) |
| | | .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; |
| | | } |
| | | } |
| | | } |