From 559890c482f5973f06aafdfec9550fee5ba3c7f2 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 09 十一月 2026 17:02:15 +0800
Subject: [PATCH] feat: CAN 模拟器 TCP 化 + 多设备并发调试 + 后台生命周期管理

---
 Plugin.DeviceConfig/Model/DeviceConfigModel.cs |  140 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 138 insertions(+), 2 deletions(-)

diff --git a/Plugin.DeviceConfig/Model/DeviceConfigModel.cs b/Plugin.DeviceConfig/Model/DeviceConfigModel.cs
index d679925..1d3b5e2 100644
--- a/Plugin.DeviceConfig/Model/DeviceConfigModel.cs
+++ b/Plugin.DeviceConfig/Model/DeviceConfigModel.cs
@@ -49,6 +49,11 @@
                         DriverType TEXT NOT NULL,
                         Transport TEXT NOT NULL,
                         Endpoint TEXT NOT NULL,
+                        ExecutionMode TEXT NOT NULL DEFAULT 'Local',
+                        AuthorityId TEXT NOT NULL DEFAULT '',
+                        BrokerId TEXT NOT NULL DEFAULT '',
+                        OwnerProcessId TEXT NOT NULL DEFAULT '',
+                        PhysicalSessionKey TEXT NOT NULL DEFAULT '',
                         EffectiveVersion TEXT NOT NULL DEFAULT '',
                         CreatedAt TEXT NOT NULL,
                         UpdatedAt TEXT NOT NULL
@@ -101,10 +106,39 @@
                 };
 
                 helper.ExecuteBatchSql(sqls);
+
+                // V3.0 搂05锛氭墽琛屼綅缃瓧娈佃縼绉伙紙鏃у簱琛ュ垪锛屾柊搴撳凡鍦ㄥ缓琛ㄨ鍙ヤ腑鍖呭惈锛�
+                MigrateExecutionColumns(helper);
             }
             catch (Exception ex)
             {
                 throw new Exception($"鍒濆鍖栬澶囬厤缃暟鎹簱澶辫触: {ex.Message}", ex);
+            }
+        }
+
+        /// <summary>
+        /// V3.0锛氭棫鐗� DeviceDefinition 琛ㄨˉ鍏呮墽琛屼綅缃簲鍒楋紙SQLite 鏃犺绾ч攣鍋囪锛岄�愬垪 PRAGMA 妫�鏌ュ悗 ALTER锛�
+        /// </summary>
+        private static void MigrateExecutionColumns(HMI.Core.Util.SqliteHelper helper)
+        {
+            var dt = helper.GetDataTable("PRAGMA table_info(DeviceDefinition);");
+            var existing = new HashSet<string>(
+                dt.Rows.Cast<DataRow>().Select(r => r["name"].ToString()), StringComparer.OrdinalIgnoreCase);
+
+            var additions = new (string Col, string Ddl)[]
+            {
+                ("ExecutionMode", "ALTER TABLE DeviceDefinition ADD COLUMN ExecutionMode TEXT NOT NULL DEFAULT 'Local';"),
+                ("AuthorityId", "ALTER TABLE DeviceDefinition ADD COLUMN AuthorityId TEXT NOT NULL DEFAULT '';"),
+                ("BrokerId", "ALTER TABLE DeviceDefinition ADD COLUMN BrokerId TEXT NOT NULL DEFAULT '';"),
+                ("OwnerProcessId", "ALTER TABLE DeviceDefinition ADD COLUMN OwnerProcessId TEXT NOT NULL DEFAULT '';"),
+                ("PhysicalSessionKey", "ALTER TABLE DeviceDefinition ADD COLUMN PhysicalSessionKey TEXT NOT NULL DEFAULT '';"),
+            };
+            foreach (var (col, ddl) in additions)
+            {
+                if (!existing.Contains(col))
+                {
+                    helper.ExecuteSql(ddl);
+                }
             }
         }
 
@@ -156,13 +190,18 @@
                 device.UpdatedAt = DateTime.Now;
 
                 var sql = $@"INSERT OR REPLACE INTO DeviceDefinition 
-                    (DeviceId, Kind, DriverType, Transport, Endpoint, EffectiveVersion, CreatedAt, UpdatedAt) 
+                    (DeviceId, Kind, DriverType, Transport, Endpoint, ExecutionMode, AuthorityId, BrokerId, OwnerProcessId, PhysicalSessionKey, EffectiveVersion, CreatedAt, UpdatedAt) 
                     VALUES (
                         '{Escape(device.DeviceId)}',
                         '{device.Kind}',
                         '{Escape(device.DriverType)}',
                         '{device.Transport}',
                         '{Escape(device.Endpoint)}',
+                        '{device.ExecutionMode}',
+                        '{Escape(device.AuthorityId)}',
+                        '{Escape(device.BrokerId)}',
+                        '{Escape(device.OwnerProcessId)}',
+                        '{Escape(device.PhysicalSessionKey)}',
                         '{Escape(device.EffectiveVersion)}',
                         '{device.CreatedAt:yyyy-MM-dd HH:mm:ss}',
                         '{device.UpdatedAt:yyyy-MM-dd HH:mm:ss}'
@@ -203,12 +242,23 @@
                     DriverType = row["DriverType"].ToString() ?? string.Empty,
                     Transport = Enum.TryParse<TransportType>(row["Transport"].ToString(), out var tp) ? tp : TransportType.Tcp,
                     Endpoint = row["Endpoint"].ToString() ?? string.Empty,
+                    ExecutionMode = HasColumn(row.Table, "ExecutionMode") && Enum.TryParse<ExecutionModeKind>(row["ExecutionMode"].ToString(), out var em)
+                        ? em : ExecutionModeKind.Local,
+                    AuthorityId = HasColumn(row.Table, "AuthorityId") ? row["AuthorityId"].ToString() ?? string.Empty : string.Empty,
+                    BrokerId = HasColumn(row.Table, "BrokerId") ? row["BrokerId"].ToString() ?? string.Empty : string.Empty,
+                    OwnerProcessId = HasColumn(row.Table, "OwnerProcessId") ? row["OwnerProcessId"].ToString() ?? string.Empty : string.Empty,
+                    PhysicalSessionKey = HasColumn(row.Table, "PhysicalSessionKey") ? row["PhysicalSessionKey"].ToString() ?? string.Empty : string.Empty,
                     EffectiveVersion = row["EffectiveVersion"].ToString() ?? string.Empty,
                     CreatedAt = DateTime.TryParse(row["CreatedAt"].ToString(), out var ca) ? ca : DateTime.Now,
                     UpdatedAt = DateTime.TryParse(row["UpdatedAt"].ToString(), out var ua) ? ua : DateTime.Now,
                 });
             }
             return list;
+        }
+
+        private static bool HasColumn(DataTable dt, string columnName)
+        {
+            return dt.Columns.Cast<DataColumn>().Any(c => c.ColumnName.Equals(columnName, StringComparison.OrdinalIgnoreCase));
         }
 
         #endregion
@@ -603,7 +653,13 @@
                 var deviceDt = helper.GetDataTable("SELECT * FROM DeviceDefinition ORDER BY DeviceId;");
                 foreach (DataRow row in deviceDt.Rows)
                 {
-                    sb.Append($"DEVICE:{row["DeviceId"]}|{row["Kind"]}|{row["DriverType"]}|{row["Transport"]}|{row["Endpoint"]};");
+                    // V3.0锛氭墽琛屼綅缃瓧娈佃繘鍏ラ厤缃搱甯岋紙INV-04锛欱roker 杩炴帴鎼� configHash 瀵硅处锛夛紱
+                    // OwnerProcessId 灞炶繍琛屾湡淇℃伅锛屼笉鍙備笌鍝堝笇锛岄伩鍏嶅涓婚噸鍚鑷村搱甯屾紓绉�
+                    string execMode = HasColumn(deviceDt, "ExecutionMode") ? row["ExecutionMode"].ToString() : "Local";
+                    string authId = HasColumn(deviceDt, "AuthorityId") ? row["AuthorityId"].ToString() : "";
+                    string bkId = HasColumn(deviceDt, "BrokerId") ? row["BrokerId"].ToString() : "";
+                    string sessKey = HasColumn(deviceDt, "PhysicalSessionKey") ? row["PhysicalSessionKey"].ToString() : "";
+                    sb.Append($"DEVICE:{row["DeviceId"]}|{row["Kind"]}|{row["DriverType"]}|{row["Transport"]}|{row["Endpoint"]}|{execMode}|{authId}|{bkId}|{sessKey};");
                 }
 
                 var channelDt = helper.GetDataTable("SELECT * FROM DeviceChannel ORDER BY DeviceId, ChannelId;");
@@ -630,6 +686,86 @@
 
         #endregion
 
+        #region 鍙戝竷闂ㄧ锛圴3.0 搂05锛�
+
+        /// <summary>
+        /// 鍙戝竷鍓嶆牎楠屾墽琛屼綅缃粦瀹氾細浠茶鍩熷繀濉笖鍚堟硶銆丅roker 鍩熶竴鑷存�с�佷細璇濆敮涓�瀹夸富銆�
+        /// 杩斿洖閿欒鍒楄〃锛屼负绌哄垯鍏佽鍙戝竷銆�
+        /// </summary>
+        public List<string> ValidatePublishGate()
+        {
+            var errors = new List<string>();
+            try
+            {
+                var devices = GetAllDevices();
+                var authorityCatalog = V3AuthorityCatalog.AuthorityIds;
+
+                // 1. 閫愯澶囨牎楠屾墽琛屼綅缃瓧娈�
+                foreach (var dev in devices)
+                {
+                    if (string.IsNullOrWhiteSpace(dev.AuthorityId))
+                    {
+                        errors.Add($"璁惧 [{dev.DeviceId}] 鏈�夋嫨浠茶鍩燂紙AuthorityId 蹇呭~锛屾棫鏁版嵁璇烽噸鏂颁繚瀛橈級");
+                        continue;
+                    }
+                    if (!authorityCatalog.Contains(dev.AuthorityId, StringComparer.OrdinalIgnoreCase))
+                    {
+                        errors.Add($"璁惧 [{dev.DeviceId}] 浠茶鍩� [{dev.AuthorityId}] 涓嶅湪閰嶇疆鏋氫妇鍊欓�変腑");
+                        continue;
+                    }
+
+                    if (dev.ExecutionMode == ExecutionModeKind.Local)
+                    {
+                        // Local 璁惧蹇呴』鐢辨湰鏈� Local 鍩熶徊瑁侊紙宸插垎閰嶇粰鍞竴鎵ц杩涚▼锛�
+                        if (!V3AuthorityCatalog.IsLocalAuthority(dev.AuthorityId))
+                        {
+                            errors.Add($"璁惧 [{dev.DeviceId}] 涓� Local 鐩磋繛锛屼徊瑁佸煙搴斾负 local- 鍓嶇紑锛屽疄闄呬负 [{dev.AuthorityId}]");
+                        }
+                    }
+                    else
+                    {
+                        if (string.IsNullOrWhiteSpace(dev.BrokerId))
+                        {
+                            errors.Add($"璁惧 [{dev.DeviceId}] 涓� Broker 鍏变韩锛屽繀椤绘寚瀹� BrokerId");
+                        }
+                        else if (!V3AuthorityCatalog.BrokerIds.Contains(dev.BrokerId, StringComparer.OrdinalIgnoreCase))
+                        {
+                            errors.Add($"璁惧 [{dev.DeviceId}] BrokerId [{dev.BrokerId}] 涓嶅湪閰嶇疆鏋氫妇鍊欓�変腑");
+                        }
+                        // BrokerId 涓庤澶囧煙涓�鑷达細Broker 璁惧鐨� AuthorityId 鍗冲叾 Broker 鍩�
+                        else if (!string.Equals(dev.BrokerId, dev.AuthorityId, StringComparison.OrdinalIgnoreCase))
+                        {
+                            errors.Add($"璁惧 [{dev.DeviceId}] BrokerId [{dev.BrokerId}] 涓庝徊瑁佸煙 [{dev.AuthorityId}] 涓嶄竴鑷�");
+                        }
+                        if (string.IsNullOrWhiteSpace(dev.PhysicalSessionKey))
+                        {
+                            errors.Add($"璁惧 [{dev.DeviceId}] 涓� Broker 鍏变韩锛屽繀椤绘寚瀹氱墿鐞嗕細璇濋敭 PhysicalSessionKey");
+                        }
+                    }
+                }
+
+                // 2. 鍚屼竴鐗╃悊浼氳瘽鍙兘鏈変竴涓涓伙細鐩稿悓 SessionKey 鐨勮澶囧繀椤诲綊灞炲悓涓�浠茶鍩�
+                var sessionGroups = devices
+                    .Where(d => !string.IsNullOrWhiteSpace(d.PhysicalSessionKey))
+                    .GroupBy(d => d.PhysicalSessionKey, StringComparer.OrdinalIgnoreCase);
+                foreach (var grp in sessionGroups)
+                {
+                    var domains = grp.Select(d => d.AuthorityId).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+                    if (domains.Count > 1)
+                    {
+                        errors.Add($"鐗╃悊浼氳瘽 [{grp.Key}] 琚涓徊瑁佸煙寮曠敤: {string.Join(", ", domains)}锛堝悓涓�浼氳瘽鍙兘鏈変竴涓涓伙級");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"鍙戝竷闂ㄧ鏍¢獙澶辫触: {ex.Message}", ex);
+            }
+            return errors;
+        }
+
+        #endregion
+
         #region 杈呭姪鏂规硶
 
         /// <summary>

--
Gitblit v1.9.1