wanshenmean
6 小时以前 34f1e65179910f3c02f0ac6813dbfefb4244d4d7
feat(同步服务): 添加实例同步功能并优化更新逻辑

添加实例同步API接口和前端刷新按钮
优化实例同步服务中的更新逻辑,确保配置更新后实例能正确重启
修复输送线任务中条码读取方式,改为使用标准接口
添加相关状态文件用于跟踪同步和代理状态

前端添加刷新按钮调用同步API
后端同步服务现在会正确处理已存在实例的更新
修正条码读取方式以避免潜在的空字符问题
添加状态跟踪文件用于监控系统运行状态
已添加4个文件
已修改4个文件
133 ■■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/.omc/state/agent-replay-584f6639-de9a-47e8-a9e4-f92fac6305be.jsonl 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/.omc/state/idle-notif-cooldown.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/.omc/state/mission-state.json 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/.omc/state/subagent-tracking.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Application/InstanceSyncService.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/测试工具/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
@@ -225,9 +225,9 @@
                    CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
                    DeviceProDTO? devicePro = conveyorLine.DeviceProDTOs.FirstOrDefault(x => x.DeviceProParamName == nameof(ConveyorLineDBNameNew.Barcode) && x.DeviceChildCode == sourceLineNo);
                    ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(sourceLineNo);  // æµ‹è¯•用
                    //var barcode = conveyorLine.GetValue<ConveyorLineDBNameNew, string>(ConveyorLineDBNameNew.Barcode, sourceLineNo);
                    stock.SourcePalletNo = string.IsNullOrEmpty(command.Barcode.Replace("\0", "").ToString()) ? string.Empty : command.Barcode.Replace("\0", "").ToString();
                    //ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(sourceLineNo);  // æµ‹è¯•用
                    var barcode = conveyorLine.GetValue<ConveyorLineDBNameNew, string>(ConveyorLineDBNameNew.Barcode, sourceLineNo);
                    stock.SourcePalletNo = string.IsNullOrEmpty(barcode) ? string.Empty : barcode;
                }
            }
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/.omc/state/agent-replay-584f6639-de9a-47e8-a9e4-f92fac6305be.jsonl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
{"t":0,"agent":"ae21cf2","agent_type":"explore","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"ae21cf2","agent_type":"explore","event":"agent_stop","success":true,"duration_ms":129137}
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/.omc/state/idle-notif-cooldown.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
{
  "lastSentAt": "2026-04-15T07:13:52.473Z"
}
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/.omc/state/mission-state.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
{
  "updatedAt": "2026-04-15T06:44:32.119Z",
  "missions": [
    {
      "id": "session:584f6639-de9a-47e8-a9e4-f92fac6305be:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-15T06:42:22.982Z",
      "updatedAt": "2026-04-15T06:44:32.119Z",
      "status": "done",
      "workerCount": 1,
      "taskCounts": {
        "total": 1,
        "pending": 0,
        "blocked": 0,
        "inProgress": 0,
        "completed": 1,
        "failed": 0
      },
      "agents": [
        {
          "name": "explore:ae21cf2",
          "role": "explore",
          "ownership": "ae21cf28e45f297ff",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-15T06:44:32.119Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:ae21cf28e45f297ff:2026-04-15T06:42:22.982Z",
          "at": "2026-04-15T06:42:22.982Z",
          "kind": "update",
          "agent": "explore:ae21cf2",
          "detail": "started explore:ae21cf2",
          "sourceKey": "session-start:ae21cf28e45f297ff"
        },
        {
          "id": "session-stop:ae21cf28e45f297ff:2026-04-15T06:44:32.119Z",
          "at": "2026-04-15T06:44:32.119Z",
          "kind": "completion",
          "agent": "explore:ae21cf2",
          "detail": "completed",
          "sourceKey": "session-stop:ae21cf28e45f297ff"
        }
      ]
    }
  ]
}
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/.omc/state/subagent-tracking.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
{
  "agents": [
    {
      "agent_id": "ae21cf28e45f297ff",
      "agent_type": "explore",
      "started_at": "2026-04-15T06:42:22.982Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-15T06:44:32.119Z",
      "duration_ms": 129137
    }
  ],
  "total_spawned": 1,
  "total_completed": 1,
  "total_failed": 0,
  "last_updated": "2026-04-15T06:44:32.224Z"
}
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Application/InstanceSyncService.cs
@@ -116,7 +116,27 @@
                // 4.4 åˆ›å»ºæˆ–更新实例
                if (_instanceManager.InstanceExists(device.DeviceCode))
                {
                    _logger.LogInformation("实例 {DeviceCode} å·²å­˜åœ¨ï¼Œè·³è¿‡åˆ›å»º", device.DeviceCode);
                    // å®žä¾‹å·²å­˜åœ¨ï¼Œæ›´æ–°é…ç½®
                    var instance = _instanceManager.GetInstance(device.DeviceCode);
                    if (instance != null)
                    {
                        var oldState = instance.GetState();
                        var wasRunning = oldState.Status == InstanceStatus.Running;
                        // åœæ­¢æ—§å®žä¾‹
                        await _instanceManager.StopInstanceAsync(device.DeviceCode);
                        // åˆ é™¤æ—§å®žä¾‹ï¼ˆä¿ç•™é…ç½®ï¼‰
                        await _instanceManager.DeleteInstanceAsync(device.DeviceCode, deleteConfig: false);
                        // é‡æ–°åˆ›å»ºå®žä¾‹
                        var newInstance = await _instanceManager.CreateInstanceAsync(config);
                        if (wasRunning)
                        {
                            await _instanceManager.StartInstanceAsync(device.DeviceCode);
                        }
                        _logger.LogInformation("已更新实例 {DeviceCode} (端口:{Port})", device.DeviceCode, device.DevicePort);
                    }
                }
                else
                {
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts
@@ -178,6 +178,18 @@
  }
}
// é‡æ–°åŒæ­¥å®žä¾‹ï¼ˆä»Žæ•°æ®åº“重新获取)
export async function syncInstances(): Promise<{ message: string; lastSyncTime: string }> {
  const response = await api.post<{ message: string; lastSyncTime: string }>('/Sync/SyncInstances')
  return response.data
}
// èŽ·å–ä¸Šæ¬¡åŒæ­¥æ—¶é—´
export async function getLastSyncTime(): Promise<{ lastSyncTime: string | null }> {
  const response = await api.get<{ lastSyncTime: string | null }>('/Sync/LastSyncTime')
  return response.data
}
// èŽ·å–æœºæ¢°æ‰‹æœåŠ¡ç«¯è¿è¡ŒçŠ¶æ€ï¼ˆåŒ…å«å¤šå®žä¾‹å’ŒæŽ¥æ”¶æ¶ˆæ¯æ—¥å¿—ï¼‰
export async function getRobotClientStatus(): Promise<RobotClientStatusResponse> {
  const response = await api.get<RobotClientStatusResponse>('/RobotClients/status')
Code/²âÊÔ¹¤¾ß/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue
@@ -13,6 +13,10 @@
        <p class="text-muted">管理和监控 S7 PLC æ¨¡æ‹Ÿå™¨å®žä¾‹</p>
      </div>
      <div class="header-right">
        <el-button type="default" @click="handleRefresh">
          <el-icon><Refresh /></el-icon>
          é‡æ–°èŽ·å–å®žä¾‹
        </el-button>
        <el-button type="primary" class="create-btn" @click="$router.push('/create')">
          <el-icon><Plus /></el-icon>
          åˆ›å»ºå®žä¾‹
@@ -173,11 +177,14 @@
import { onMounted, onUnmounted, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useInstancesStore } from '../stores/instances'
import api from '../api'
import { syncInstances } from '../api'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  Cpu,
  Plus,
  Loading,
  Refresh,
  User,
  VideoPause,
  VideoPlay,
@@ -199,6 +206,17 @@
  store.stopAutoRefresh()
})
async function handleRefresh() {
  try {
    await syncInstances()
    await store.loadInstances()
    ElMessage.success('已重新获取实例列表')
  } catch (err) {
    console.error('同步失败:', err)
    ElMessage.error('同步失败,请查看控制台')
  }
}
async function handleStart(id: string) {
  try {
    await ElMessageBox.confirm(`确定要启动实例 "${id}" å—?`, '确认', {