编辑 | blame | 历史 | 原始文档

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 包
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
  • [ ] Step 2: Commit
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
namespace WIDESEAWCS_S7Simulator.Application.DTOs;

/// <summary>
/// 设备信息 DTO(映射 WCS Dt_DeviceInfo 表)
/// </summary>
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;
}

/// <summary>
/// 设备协议 DTO(映射 WCS Dt_DeviceProtocol 表)
/// </summary>
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
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
namespace WIDESEAWCS_S7Simulator.Application;

/// <summary>
/// WCS 数据库连接配置
/// </summary>
public class WcsDbOptions
{
    public const string SectionName = "WcsDb";

    /// <summary>
    /// 是否启用数据库同步
    /// </summary>
    public bool Enabled { get; set; }

    /// <summary>
    /// 连接字符串
    /// </summary>
    public string ConnectionString { get; set; } = string.Empty;

    /// <summary>
    /// 数据库类型:SqlServer=2, MySql=1
    /// </summary>
    public int DbType { get; set; } = 2;
}
  • [ ] Step 2: Commit
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
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using WIDESEAWCS_S7Simulator.Application.DTOs;

namespace WIDESEAWCS_S7Simulator.Application;

/// <summary>
/// 读取 WCS 数据库设备数据
/// </summary>
public class DatabaseDeviceService : IDisposable
{
    private readonly WcsDbOptions _options;
    private readonly ILogger<DatabaseDeviceService> _logger;
    private readonly SqlSugarScope _db;

    public DatabaseDeviceService(IOptions<WcsDbOptions> options, ILogger<DatabaseDeviceService> logger)
    {
        _options = options.Value;
        _logger = logger;

        _db = new SqlSugarScope(new ConnectionConfig
        {
            ConnectionString = _options.ConnectionString,
            DbType = (DbType)_options.DbType,
            IsAutoCloseConnection = true,
            InitKeyType = InitKeyType.Attribute
        });
    }

    /// <summary>
    /// 获取所有 SiemensS7 类型的设备
    /// </summary>
    public async Task<List<WcsDeviceDto>> GetSiemensS7DevicesAsync()
    {
        try
        {
            var devices = await _db.Queryable<WcsDeviceDto>()
                .Where(d => d.DevicePlcType == "SiemensS7")
                .ToListAsync();
            _logger.LogInformation("从数据库获取到 {Count} 个 SiemensS7 设备", devices.Count);
            return devices;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "从数据库获取设备失败");
            return new List<WcsDeviceDto>();
        }
    }

    /// <summary>
    /// 获取指定设备的协议列表
    /// </summary>
    public async Task<List<WcsDeviceProtocolDto>> GetDeviceProtocolsAsync(int deviceId)
    {
        try
        {
            var protocols = await _db.Queryable<WcsDeviceProtocolDto>()
                .Where(p => p.DeviceId == deviceId)
                .ToListAsync();
            return protocols;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "获取设备协议失败 DeviceId={DeviceId}", deviceId);
            return new List<WcsDeviceProtocolDto>();
        }
    }

    public void Dispose()
    {
        _db.Dispose();
    }
}
  • [ ] Step 2: Commit
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
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;

/// <summary>
/// 实例同步服务:协调数据库读取、协议模板生成、实例创建
/// </summary>
public class InstanceSyncService
{
    private readonly DatabaseDeviceService _deviceService;
    private readonly ISimulatorInstanceManager _instanceManager;
    private readonly IProtocolTemplateService _templateService;
    private readonly ILogger<InstanceSyncService> _logger;

    private DateTime? _lastSyncTime;

    public DateTime? LastSyncTime => _lastSyncTime;

    public InstanceSyncService(
        DatabaseDeviceService deviceService,
        ISimulatorInstanceManager instanceManager,
        IProtocolTemplateService templateService,
        ILogger<InstanceSyncService> logger)
    {
        _deviceService = deviceService;
        _instanceManager = instanceManager;
        _templateService = templateService;
        _logger = logger;
    }

    /// <summary>
    /// 执行同步:从数据库读取设备,创建实例和协议模板
    /// </summary>
    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("同步完成");
    }

    /// <summary>
    /// 数据类型映射:数据库字符串 -> ProtocolDataType 枚举
    /// ProtocolDataType: Byte=0, Int=1, DInt=2, String=3, Bool=4
    /// </summary>
    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
        };
    }

    /// <summary>
    /// 获取默认内存配置
    /// </summary>
    private static MemoryRegionConfig GetDefaultMemoryConfig()
    {
        return new MemoryRegionConfig
        {
            MRegionSize = 1024,
            DBBlockCount = 1,
            DBBlockNumbers = new List<int> { 50 },
            DBBlockSize = 65536,
            IRegionSize = 256,
            QRegionSize = 256,
            TRegionCount = 64,
            CRegionCount = 64
        };
    }
}
  • [ ] Step 2: Commit
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
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<SyncController> _logger;

    public SyncController(InstanceSyncService syncService, ILogger<SyncController> logger)
    {
        _syncService = syncService;
        _logger = logger;
    }

    /// <summary>
    /// 手动触发实例同步
    /// </summary>
    [HttpPost("SyncInstances")]
    public async Task<IActionResult> 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 });
        }
    }

    /// <summary>
    /// 获取上次同步时间
    /// </summary>
    [HttpGet("LastSyncTime")]
    public IActionResult GetLastSyncTime()
    {
        return Ok(new { lastSyncTime = _syncService.LastSyncTime });
    }
}
  • [ ] Step 2: Commit
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 根对象添加:

"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
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() 之后添加:

// WCS 数据库配置
builder.Services.Configure<WcsDbOptions>(builder.Configuration.GetSection(WcsDbOptions.SectionName));
builder.Services.AddSingleton<DatabaseDeviceService>();
builder.Services.AddSingleton<InstanceSyncService>();

app.MapControllers() 之后,将同步整合到现有的 Task.Run 中:

// 启动时加载已保存的实例(不自动启动)
var instanceManager = app.Services.GetRequiredService<ISimulatorInstanceManager>();
var syncService = app.Services.GetRequiredService<InstanceSyncService>();
_ = Task.Run(async () =>
{
    try
    {
        // 先加载已保存的实例
        await instanceManager.LoadSavedInstancesAsync(autoStart: false);
        Console.WriteLine($"Loaded {instanceManager.GetAllInstances().Count()} saved instances.");

        // 如果启用了 WCS 数据库同步,则执行同步
        var wcsDbOptions = app.Services.GetRequiredService<IOptions<WcsDbOptions>>().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
git add WIDESEAWCS_S7Simulator.Server/Program.cs
git commit -m "feat: Program.cs 注册同步服务并启动时同步"

Task 9: 构建验证

  • [ ] Step 1: 运行 dotnet build 验证编译
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 - 构建验证