# S7 模拟器数据库实例同步实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 从 WCS 数据库自动同步设备到 S7 模拟器实例,启动时同步一次,也支持 API 手动触发 **Architecture:** 在 Application 层新增 DatabaseDeviceService 读取 WCS 数据库,InstanceSyncService 协调同步逻辑,SimulatorInstanceManager 创建/管理实例。协议模板通过现有 IProtocolTemplateService 保存到本地 JSON 文件。 **Tech Stack:** .NET 6, SqlSugarCore, ASP.NET Core, JSON 文件持久化 --- ## 文件结构 ``` Application/ ├── DTOs/ │ └── WcsDeviceDto.cs # 数据库字段映射 DTO ├── WcsDbOptions.cs # WCS 数据库配置 ├── DatabaseDeviceService.cs # 读取 WCS 数据库 └── InstanceSyncService.cs # 同步协调逻辑 Server/ ├── Controllers/ │ └── SyncController.cs # API 接口 ├── appsettings.json # 添加 WcsDb 配置 └── Program.cs # 注册服务、启动同步 ``` --- ## Task 1: 添加 SqlSugarCore 依赖 **Files:** - Modify: `WIDESEAWCS_S7Simulator.Application/WIDESEAWCS_S7Simulator.Application.csproj` - [ ] **Step 1: 添加 SqlSugarCore NuGet 包** ```xml ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Application/WIDESEAWCS_S7Simulator.Application.csproj git commit -m "feat: 添加 SqlSugarCore 依赖用于数据库连接" ``` --- ## Task 2: 创建 WCS 数据库 DTO **Files:** - Create: `WIDESEAWCS_S7Simulator.Application/DTOs/WcsDeviceDto.cs` - [ ] **Step 1: 创建 WcsDeviceDto.cs** ```csharp namespace WIDESEAWCS_S7Simulator.Application.DTOs; /// /// 设备信息 DTO(映射 WCS Dt_DeviceInfo 表) /// public class WcsDeviceDto { public int Id { get; set; } public string DeviceCode { get; set; } = string.Empty; public string DeviceName { get; set; } = string.Empty; public string DeviceType { get; set; } = string.Empty; public string DeviceStatus { get; set; } = string.Empty; public string DeviceIp { get; set; } = string.Empty; public int DevicePort { get; set; } public string DevicePlcType { get; set; } = string.Empty; public string DeviceRemark { get; set; } = string.Empty; } /// /// 设备协议 DTO(映射 WCS Dt_DeviceProtocol 表) /// public class WcsDeviceProtocolDto { public int Id { get; set; } public int DeviceId { get; set; } public string DeviceChildCode { get; set; } = string.Empty; public string DeviceProDataBlock { get; set; } = string.Empty; public decimal DeviceProOffset { get; set; } public string DeviceProDataType { get; set; } = string.Empty; public int DeviceProDataLength { get; set; } public string DeviceProParamName { get; set; } = string.Empty; public string DeviceProParamType { get; set; } = string.Empty; public string DeviceProParamDes { get; set; } = string.Empty; public string DeviceProRemark { get; set; } = string.Empty; } ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Application/DTOs/WcsDeviceDto.cs git commit -m "feat: 添加 WCS 设备 DTO" ``` --- ## Task 3: 创建 WcsDbOptions 配置类 **Files:** - Create: `WIDESEAWCS_S7Simulator.Application/WcsDbOptions.cs` - [ ] **Step 1: 创建 WcsDbOptions.cs** ```csharp namespace WIDESEAWCS_S7Simulator.Application; /// /// WCS 数据库连接配置 /// public class WcsDbOptions { public const string SectionName = "WcsDb"; /// /// 是否启用数据库同步 /// public bool Enabled { get; set; } /// /// 连接字符串 /// public string ConnectionString { get; set; } = string.Empty; /// /// 数据库类型:SqlServer=2, MySql=1 /// public int DbType { get; set; } = 2; } ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Application/WcsDbOptions.cs git commit -m "feat: 添加 WcsDbOptions 配置类" ``` --- ## Task 4: 创建 DatabaseDeviceService **Files:** - Create: `WIDESEAWCS_S7Simulator.Application/DatabaseDeviceService.cs` - [ ] **Step 1: 创建 DatabaseDeviceService.cs** ```csharp using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SqlSugar; using WIDESEAWCS_S7Simulator.Application.DTOs; namespace WIDESEAWCS_S7Simulator.Application; /// /// 读取 WCS 数据库设备数据 /// public class DatabaseDeviceService : IDisposable { private readonly WcsDbOptions _options; private readonly ILogger _logger; private readonly SqlSugarScope _db; public DatabaseDeviceService(IOptions options, ILogger logger) { _options = options.Value; _logger = logger; _db = new SqlSugarScope(new ConnectionConfig { ConnectionString = _options.ConnectionString, DbType = (DbType)_options.DbType, IsAutoCloseConnection = true, InitKeyType = InitKeyType.Attribute }); } /// /// 获取所有 SiemensS7 类型的设备 /// public async Task> GetSiemensS7DevicesAsync() { try { var devices = await _db.Queryable() .Where(d => d.DevicePlcType == "SiemensS7") .ToListAsync(); _logger.LogInformation("从数据库获取到 {Count} 个 SiemensS7 设备", devices.Count); return devices; } catch (Exception ex) { _logger.LogError(ex, "从数据库获取设备失败"); return new List(); } } /// /// 获取指定设备的协议列表 /// public async Task> GetDeviceProtocolsAsync(int deviceId) { try { var protocols = await _db.Queryable() .Where(p => p.DeviceId == deviceId) .ToListAsync(); return protocols; } catch (Exception ex) { _logger.LogError(ex, "获取设备协议失败 DeviceId={DeviceId}", deviceId); return new List(); } } public void Dispose() { _db.Dispose(); } } ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Application/DatabaseDeviceService.cs git commit -m "feat: 添加 DatabaseDeviceService 读取 WCS 数据库" ``` --- ## Task 5: 创建 InstanceSyncService **Files:** - Create: `WIDESEAWCS_S7Simulator.Application/InstanceSyncService.cs` - [ ] **Step 1: 创建 InstanceSyncService.cs** ```csharp using Microsoft.Extensions.Logging; using WIDESEAWCS_S7Simulator.Application.Protocol; using WIDESEAWCS_S7Simulator.Core.Entities; using WIDESEAWCS_S7Simulator.Core.Enums; using WIDESEAWCS_S7Simulator.Core.Interfaces; using WIDESEAWCS_S7Simulator.Core.Protocol; namespace WIDESEAWCS_S7Simulator.Application; /// /// 实例同步服务:协调数据库读取、协议模板生成、实例创建 /// public class InstanceSyncService { private readonly DatabaseDeviceService _deviceService; private readonly ISimulatorInstanceManager _instanceManager; private readonly IProtocolTemplateService _templateService; private readonly ILogger _logger; private DateTime? _lastSyncTime; public DateTime? LastSyncTime => _lastSyncTime; public InstanceSyncService( DatabaseDeviceService deviceService, ISimulatorInstanceManager instanceManager, IProtocolTemplateService templateService, ILogger logger) { _deviceService = deviceService; _instanceManager = instanceManager; _templateService = templateService; _logger = logger; } /// /// 执行同步:从数据库读取设备,创建实例和协议模板 /// public async Task SyncInstancesAsync() { _logger.LogInformation("开始同步实例..."); _lastSyncTime = DateTime.Now; // 1. 获取所有 SiemensS7 设备 var devices = await _deviceService.GetSiemensS7DevicesAsync(); if (devices.Count == 0) { _logger.LogWarning("未找到 SiemensS7 设备,同步取消"); return; } // 2. 获取现有实例ID列表 var existingInstanceIds = _instanceManager.GetAllInstances().Select(i => i.Config.Id).ToHashSet(StringComparer.OrdinalIgnoreCase); var dbDeviceCodes = devices.Select(d => d.DeviceCode).ToHashSet(StringComparer.OrdinalIgnoreCase); // 3. 清理已不存在的实例(数据库中没有但内存中有) foreach (var instanceId in existingInstanceIds) { if (!dbDeviceCodes.Contains(instanceId)) { _logger.LogInformation("数据库中已删除设备 {InstanceId},从内存移除", instanceId); await _instanceManager.DeleteInstanceAsync(instanceId, deleteConfig: false); } } // 4. 创建或更新实例 foreach (var device in devices) { try { // 4.1 获取设备协议 var protocols = await _deviceService.GetDeviceProtocolsAsync(device.Id); // 4.2 创建协议模板 var templateId = $"protocol-{device.DeviceCode}"; var template = new ProtocolTemplate { Id = templateId, Name = $"{device.DeviceName} 协议模板", Version = "1.0", Fields = protocols.Select(p => new ProtocolFieldMapping { FieldKey = p.DeviceProParamName, DbNumber = int.TryParse(p.DeviceProDataBlock, out var dbNum) ? dbNum : 50, Offset = (int)p.DeviceProOffset, Bit = 1, DataType = MapDataType(p.DeviceProDataType), Length = p.DeviceProDataLength, Direction = ProtocolFieldDirection.Bidirectional }).ToList() }; await _templateService.UpsertAsync(template); // 4.3 创建实例配置 var config = new InstanceConfig { Id = device.DeviceCode, Name = device.DeviceName, PLCType = SiemensPLCType.S71500, // 默认 S7-1500,可根据需要扩展 Port = device.DevicePort, AutoStart = false, ProtocolTemplateId = templateId, MemoryConfig = GetDefaultMemoryConfig() }; // 4.4 创建或更新实例 if (_instanceManager.InstanceExists(device.DeviceCode)) { _logger.LogInformation("实例 {DeviceCode} 已存在,跳过创建", device.DeviceCode); } else { await _instanceManager.CreateInstanceAsync(config); _logger.LogInformation("已创建实例 {DeviceCode} (端口:{Port})", device.DeviceCode, device.DevicePort); } } catch (Exception ex) { _logger.LogError(ex, "同步设备 {DeviceCode} 失败", device.DeviceCode); } } _logger.LogInformation("同步完成"); } /// /// 数据类型映射:数据库字符串 -> ProtocolDataType 枚举 /// ProtocolDataType: Byte=0, Int=1, DInt=2, String=3, Bool=4 /// private static ProtocolDataType MapDataType(string? dbDataType) { return dbDataType?.ToLowerInvariant() switch { "bit" => ProtocolDataType.Bool, // Bool=4 "byte" => ProtocolDataType.Byte, // Byte=0 "int" or "word" => ProtocolDataType.Int, // Int=1 "dint" => ProtocolDataType.DInt, // DInt=2 "string" or "string8" or "string16" => ProtocolDataType.String, // String=3 _ => ProtocolDataType.Byte }; } /// /// 获取默认内存配置 /// private static MemoryRegionConfig GetDefaultMemoryConfig() { return new MemoryRegionConfig { MRegionSize = 1024, DBBlockCount = 1, DBBlockNumbers = new List { 50 }, DBBlockSize = 65536, IRegionSize = 256, QRegionSize = 256, TRegionCount = 64, CRegionCount = 64 }; } } ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Application/InstanceSyncService.cs git commit -m "feat: 添加 InstanceSyncService 同步协调逻辑" ``` --- ## Task 6: 创建 SyncController **Files:** - Create: `WIDESEAWCS_S7Simulator.Server/Controllers/SyncController.cs` - [ ] **Step 1: 创建 SyncController.cs** ```csharp using Microsoft.AspNetCore.Mvc; using WIDESEAWCS_S7Simulator.Application; namespace WIDESEAWCS_S7Simulator.Server.Controllers; [ApiController] [Route("api/[controller]")] public class SyncController : ControllerBase { private readonly InstanceSyncService _syncService; private readonly ILogger _logger; public SyncController(InstanceSyncService syncService, ILogger logger) { _syncService = syncService; _logger = logger; } /// /// 手动触发实例同步 /// [HttpPost("SyncInstances")] public async Task SyncInstances() { try { _logger.LogInformation("收到手动同步请求"); await _syncService.SyncInstancesAsync(); return Ok(new { message = "同步完成", lastSyncTime = _syncService.LastSyncTime }); } catch (Exception ex) { _logger.LogError(ex, "手动同步失败"); return StatusCode(500, new { message = "同步失败", error = ex.Message }); } } /// /// 获取上次同步时间 /// [HttpGet("LastSyncTime")] public IActionResult GetLastSyncTime() { return Ok(new { lastSyncTime = _syncService.LastSyncTime }); } } ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Server/Controllers/SyncController.cs git commit -m "feat: 添加 SyncController API 接口" ``` --- ## Task 7: 修改 appsettings.json 添加 WcsDb 配置 **Files:** - Modify: `WIDESEAWCS_S7Simulator.Server/appsettings.json` - [ ] **Step 1: 添加 WcsDb 配置节** 在 appsettings.json 根对象添加: ```json "WcsDb": { "Enabled": true, "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWCS_ShanMei;User ID=sa;Password=${WCS_DB_PASSWORD};Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "DbType": 2 } ``` 注意:`${WCS_DB_PASSWORD}` 为环境变量占位符,实际部署时替换为真实密码或通过环境变量注入。 - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Server/appsettings.json git commit -m "feat: 添加 WcsDb 数据库配置" ``` --- ## Task 8: 修改 Program.cs 注册服务和启动同步 **Files:** - Modify: `WIDESEAWCS_S7Simulator.Server/Program.cs` - [ ] **Step 1: 注册服务并启动时同步** 在 `builder.Services.AddControllers()` 之后添加: ```csharp // WCS 数据库配置 builder.Services.Configure(builder.Configuration.GetSection(WcsDbOptions.SectionName)); builder.Services.AddSingleton(); builder.Services.AddSingleton(); ``` 在 `app.MapControllers()` 之后,将同步整合到现有的 `Task.Run` 中: ```csharp // 启动时加载已保存的实例(不自动启动) var instanceManager = app.Services.GetRequiredService(); var syncService = app.Services.GetRequiredService(); _ = Task.Run(async () => { try { // 先加载已保存的实例 await instanceManager.LoadSavedInstancesAsync(autoStart: false); Console.WriteLine($"Loaded {instanceManager.GetAllInstances().Count()} saved instances."); // 如果启用了 WCS 数据库同步,则执行同步 var wcsDbOptions = app.Services.GetRequiredService>().Value; if (wcsDbOptions.Enabled) { await syncService.SyncInstancesAsync(); Console.WriteLine($"WCS DB sync completed. Last sync: {syncService.LastSyncTime}"); } } catch (Exception ex) { Console.WriteLine($"Error during startup: {ex.Message}"); } }); ``` - [ ] **Step 2: Commit** ```bash git add WIDESEAWCS_S7Simulator.Server/Program.cs git commit -m "feat: Program.cs 注册同步服务并启动时同步" ``` --- ## Task 9: 构建验证 - [ ] **Step 1: 运行 dotnet build 验证编译** ```bash cd WIDESEAWCS_S7Simulator.Server dotnet build ``` 预期:无编译错误 - [ ] **Step 2: Commit any last changes if needed** --- ## 执行顺序 1. Task 1 - 添加 SqlSugarCore 依赖 2. Task 2 - 创建 WCS 数据库 DTO 3. Task 3 - 创建 WcsDbOptions 配置类 4. Task 4 - 创建 DatabaseDeviceService 5. Task 5 - 创建 InstanceSyncService 6. Task 6 - 创建 SyncController 7. Task 7 - 修改 appsettings.json 8. Task 8 - 修改 Program.cs 9. Task 9 - 构建验证