using Microsoft.AspNetCore.Mvc;
|
using WIDESEAWCS_S7Simulator.Application.Protocol;
|
using WIDESEAWCS_S7Simulator.Core.Entities;
|
using WIDESEAWCS_S7Simulator.Core.Interfaces;
|
|
namespace WIDESEAWCS_S7Simulator.Server.Controllers
|
{
|
/// <summary>
|
/// 仿真器实例管理控制器
|
/// </summary>
|
[ApiController]
|
[Route("api/[controller]")]
|
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("GetAll")]
|
[ProducesResponseType(typeof(IEnumerable<object>), 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" });
|
}
|
}
|
|
/// <summary>
|
/// 创建新实例
|
/// </summary>
|
[HttpPost("Create")]
|
[ProducesResponseType(typeof(object), StatusCodes.Status201Created)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 获取指定实例详情
|
/// </summary>
|
[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" });
|
}
|
}
|
|
/// <summary>
|
/// 获取指定实例的配置
|
/// </summary>
|
[HttpGet("GetInstanceConfig")]
|
[ProducesResponseType(typeof(InstanceConfig), StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
public ActionResult<InstanceConfig> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 更新实例配置
|
/// </summary>
|
[HttpPut("Update")]
|
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 删除实例
|
/// </summary>
|
[HttpDelete("Delete")]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
public async Task<IActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 启动实例
|
/// </summary>
|
[HttpPost("start")]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 停止实例
|
/// </summary>
|
[HttpPost("stop")]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 重启实例
|
/// </summary>
|
[HttpPost("restart")]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 启动所有自动启动实例
|
/// </summary>
|
[HttpPost("start-all")]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <summary>
|
/// 停止所有实例
|
/// </summary>
|
[HttpPost("stop-all")]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
public async Task<ActionResult> 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" });
|
}
|
}
|
|
/// <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;
|
}
|
}
|
}
|