wenxiangchao
2026-11-02 9d1660270272180cd71147d6ddd53d1382643d2e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using HMI.Core.Util;
using Plugin.DeviceConfig.Entity;
 
namespace Plugin.DeviceConfig.Model
{
    /// <summary>
    /// 设备配置数据访问层,基于 SQLite 实现
    /// </summary>
    public class DeviceConfigModel
    {
        private string _dbPath = string.Empty;
 
        /// <summary>
        /// 初始化数据库,创建所有表
        /// </summary>
        /// <param name="dbPath">数据库文件路径</param>
        public void InitDatabase(string dbPath)
        {
            _dbPath = dbPath;
            try
            {
                // 确保目录存在
                string? dir = System.IO.Path.GetDirectoryName(dbPath);
                if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir))
                {
                    System.IO.Directory.CreateDirectory(dir);
                }
 
                // 仅在数据库文件不存在时创建(避免重启后抛异常或丢失已有数据)
                if (!System.IO.File.Exists(dbPath))
                {
                    SqliteHelper.CreateDatabase(dbPath);
                }
 
                using var helper = new SqliteHelper(dbPath);
 
                var sqls = new List<string>
                {
                    // 设备定义表
                    @"CREATE TABLE IF NOT EXISTS DeviceDefinition (
                        DeviceId TEXT PRIMARY KEY,
                        Kind TEXT NOT NULL,
                        DriverType TEXT NOT NULL,
                        Transport TEXT NOT NULL,
                        Endpoint TEXT NOT NULL,
                        EffectiveVersion TEXT NOT NULL DEFAULT '',
                        CreatedAt TEXT NOT NULL,
                        UpdatedAt TEXT NOT NULL
                    );",
 
                    // 设备通道表
                    @"CREATE TABLE IF NOT EXISTS DeviceChannel (
                        DeviceId TEXT NOT NULL,
                        ChannelId INTEGER NOT NULL,
                        Capability TEXT NOT NULL DEFAULT '',
                        MaxVoltage REAL NOT NULL DEFAULT 0,
                        MaxCurrent REAL NOT NULL DEFAULT 0,
                        Limits TEXT NOT NULL DEFAULT '{}',
                        SessionKey TEXT NOT NULL DEFAULT '',
                        PRIMARY KEY (DeviceId, ChannelId)
                    );",
 
                    // Slot映射表
                    @"CREATE TABLE IF NOT EXISTS SlotDeviceMapping (
                        SlotId TEXT NOT NULL,
                        Capability TEXT NOT NULL,
                        Version TEXT NOT NULL,
                        DeviceId TEXT NOT NULL,
                        ChannelId INTEGER NOT NULL DEFAULT 0,
                        MappingVersion TEXT NOT NULL DEFAULT '',
                        PRIMARY KEY (SlotId, Capability, Version)
                    );",
 
                    // 配置版本表
                    @"CREATE TABLE IF NOT EXISTS DeviceConfigVersion (
                        VersionId TEXT PRIMARY KEY,
                        Status TEXT NOT NULL,
                        Hash TEXT NOT NULL DEFAULT '',
                        Author TEXT NOT NULL DEFAULT '',
                        Timestamp TEXT NOT NULL,
                        DiffDescription TEXT NOT NULL DEFAULT ''
                    );",
 
                    // 设备组模板表
                    @"CREATE TABLE IF NOT EXISTS DeviceGroupTemplate (
                        TemplateId TEXT PRIMARY KEY,
                        DeviceType TEXT NOT NULL DEFAULT '',
                        DriverType TEXT NOT NULL DEFAULT '',
                        Transport TEXT NOT NULL DEFAULT '',
                        DeviceCount INTEGER NOT NULL DEFAULT 0,
                        ChannelsPerDevice INTEGER NOT NULL DEFAULT 0,
                        AddressRule TEXT NOT NULL DEFAULT '',
                        SlotStart INTEGER NOT NULL DEFAULT 0
                    );"
                };
 
                helper.ExecuteBatchSql(sqls);
            }
            catch (Exception ex)
            {
                throw new Exception($"初始化设备配置数据库失败: {ex.Message}", ex);
            }
        }
 
        #region DeviceDefinition CRUD
 
        /// <summary>
        /// 获取所有设备定义
        /// </summary>
        public List<DeviceDefinition> GetAllDevices()
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var dt = helper.GetDataTable("SELECT * FROM DeviceDefinition ORDER BY CreatedAt DESC;");
                return MapToDeviceList(dt);
            }
            catch (Exception ex)
            {
                throw new Exception($"获取所有设备定义失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 根据ID获取设备定义
        /// </summary>
        public DeviceDefinition? GetDeviceById(string deviceId)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var dt = helper.GetDataTable($"SELECT * FROM DeviceDefinition WHERE DeviceId = '{Escape(deviceId)}';");
                var list = MapToDeviceList(dt);
                return list.Count > 0 ? list[0] : null;
            }
            catch (Exception ex)
            {
                throw new Exception($"获取设备定义[{deviceId}]失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 保存设备定义(新增或更新)
        /// </summary>
        public void SaveDevice(DeviceDefinition device)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                device.UpdatedAt = DateTime.Now;
 
                var sql = $@"INSERT OR REPLACE INTO DeviceDefinition 
                    (DeviceId, Kind, DriverType, Transport, Endpoint, EffectiveVersion, CreatedAt, UpdatedAt) 
                    VALUES (
                        '{Escape(device.DeviceId)}',
                        '{device.Kind}',
                        '{Escape(device.DriverType)}',
                        '{device.Transport}',
                        '{Escape(device.Endpoint)}',
                        '{Escape(device.EffectiveVersion)}',
                        '{device.CreatedAt:yyyy-MM-dd HH:mm:ss}',
                        '{device.UpdatedAt:yyyy-MM-dd HH:mm:ss}'
                    );";
                helper.ExecuteSql(sql);
            }
            catch (Exception ex)
            {
                throw new Exception($"保存设备定义[{device.DeviceId}]失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 删除设备定义
        /// </summary>
        public void DeleteDevice(string deviceId)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                helper.ExecuteSql($"DELETE FROM DeviceDefinition WHERE DeviceId = '{Escape(deviceId)}';");
            }
            catch (Exception ex)
            {
                throw new Exception($"删除设备定义[{deviceId}]失败: {ex.Message}", ex);
            }
        }
 
        private List<DeviceDefinition> MapToDeviceList(DataTable dt)
        {
            var list = new List<DeviceDefinition>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(new DeviceDefinition
                {
                    DeviceId = row["DeviceId"].ToString() ?? string.Empty,
                    Kind = Enum.TryParse<DeviceKind>(row["Kind"].ToString(), out var kind) ? kind : DeviceKind.PowerSupply,
                    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,
                    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;
        }
 
        #endregion
 
        #region DeviceChannel CRUD
 
        /// <summary>
        /// 获取指定设备的所有通道
        /// </summary>
        public List<DeviceChannel> GetChannelsByDevice(string deviceId)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var dt = helper.GetDataTable($"SELECT * FROM DeviceChannel WHERE DeviceId = '{Escape(deviceId)}' ORDER BY ChannelId;");
                return MapToChannelList(dt);
            }
            catch (Exception ex)
            {
                throw new Exception($"获取设备[{deviceId}]通道失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 保存设备通道(新增或更新)
        /// </summary>
        public void SaveChannel(DeviceChannel channel)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var sql = $@"INSERT OR REPLACE INTO DeviceChannel 
                    (DeviceId, ChannelId, Capability, MaxVoltage, MaxCurrent, Limits, SessionKey) 
                    VALUES (
                        '{Escape(channel.DeviceId)}',
                        {channel.ChannelId},
                        '{Escape(channel.Capability)}',
                        {channel.MaxVoltage},
                        {channel.MaxCurrent},
                        '{Escape(channel.Limits)}',
                        '{Escape(channel.SessionKey)}'
                    );";
                helper.ExecuteSql(sql);
            }
            catch (Exception ex)
            {
                throw new Exception($"保存通道[{channel.DeviceId}:{channel.ChannelId}]失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 删除设备通道
        /// </summary>
        public void DeleteChannel(string deviceId, int channelId)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                helper.ExecuteSql($"DELETE FROM DeviceChannel WHERE DeviceId = '{Escape(deviceId)}' AND ChannelId = {channelId};");
            }
            catch (Exception ex)
            {
                throw new Exception($"删除通道[{deviceId}:{channelId}]失败: {ex.Message}", ex);
            }
        }
 
        private List<DeviceChannel> MapToChannelList(DataTable dt)
        {
            var list = new List<DeviceChannel>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(new DeviceChannel
                {
                    DeviceId = row["DeviceId"].ToString() ?? string.Empty,
                    ChannelId = Convert.ToInt32(row["ChannelId"]),
                    Capability = row["Capability"].ToString() ?? string.Empty,
                    MaxVoltage = Convert.ToDouble(row["MaxVoltage"]),
                    MaxCurrent = Convert.ToDouble(row["MaxCurrent"]),
                    Limits = row["Limits"].ToString() ?? "{}",
                    SessionKey = row["SessionKey"].ToString() ?? string.Empty,
                });
            }
            return list;
        }
 
        #endregion
 
        #region SlotDeviceMapping CRUD
 
        /// <summary>
        /// 获取所有Slot映射
        /// </summary>
        public List<SlotDeviceMapping> GetSlotMappings()
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var dt = helper.GetDataTable("SELECT * FROM SlotDeviceMapping ORDER BY SlotId;");
                return MapToSlotMappingList(dt);
            }
            catch (Exception ex)
            {
                throw new Exception($"获取Slot映射失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 保存Slot映射(新增或更新)
        /// </summary>
        public void SaveSlotMapping(SlotDeviceMapping mapping)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var sql = $@"INSERT OR REPLACE INTO SlotDeviceMapping 
                    (SlotId, Capability, Version, DeviceId, ChannelId, MappingVersion) 
                    VALUES (
                        '{Escape(mapping.SlotId)}',
                        '{Escape(mapping.Capability)}',
                        '{Escape(mapping.Version)}',
                        '{Escape(mapping.DeviceId)}',
                        {mapping.ChannelId},
                        '{Escape(mapping.MappingVersion)}'
                    );";
                helper.ExecuteSql(sql);
            }
            catch (Exception ex)
            {
                throw new Exception($"保存Slot映射[{mapping.SlotId}]失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 删除Slot映射
        /// </summary>
        public void DeleteSlotMapping(string slotId, string capability, string version)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                helper.ExecuteSql($@"DELETE FROM SlotDeviceMapping 
                    WHERE SlotId = '{Escape(slotId)}' 
                    AND Capability = '{Escape(capability)}' 
                    AND Version = '{Escape(version)}';");
            }
            catch (Exception ex)
            {
                throw new Exception($"删除Slot映射[{slotId}:{capability}:{version}]失败: {ex.Message}", ex);
            }
        }
 
        private List<SlotDeviceMapping> MapToSlotMappingList(DataTable dt)
        {
            var list = new List<SlotDeviceMapping>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(new SlotDeviceMapping
                {
                    SlotId = row["SlotId"].ToString() ?? string.Empty,
                    Capability = row["Capability"].ToString() ?? string.Empty,
                    Version = row["Version"].ToString() ?? string.Empty,
                    DeviceId = row["DeviceId"].ToString() ?? string.Empty,
                    ChannelId = Convert.ToInt32(row["ChannelId"]),
                    MappingVersion = row["MappingVersion"].ToString() ?? string.Empty,
                });
            }
            return list;
        }
 
        #endregion
 
        #region DeviceConfigVersion CRUD
 
        /// <summary>
        /// 获取所有配置版本
        /// </summary>
        public List<DeviceConfigVersion> GetConfigVersions()
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var dt = helper.GetDataTable("SELECT * FROM DeviceConfigVersion ORDER BY Timestamp DESC;");
                return MapToVersionList(dt);
            }
            catch (Exception ex)
            {
                throw new Exception($"获取配置版本失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 保存配置版本(新增或更新)
        /// </summary>
        public void SaveConfigVersion(DeviceConfigVersion version)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var sql = $@"INSERT OR REPLACE INTO DeviceConfigVersion 
                    (VersionId, Status, Hash, Author, Timestamp, DiffDescription) 
                    VALUES (
                        '{Escape(version.VersionId)}',
                        '{version.Status}',
                        '{Escape(version.Hash)}',
                        '{Escape(version.Author)}',
                        '{version.Timestamp:yyyy-MM-dd HH:mm:ss}',
                        '{Escape(version.DiffDescription)}'
                    );";
                helper.ExecuteSql(sql);
            }
            catch (Exception ex)
            {
                throw new Exception($"保存配置版本[{version.VersionId}]失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 更新版本状态
        /// </summary>
        public void UpdateVersionStatus(string versionId, string newStatus)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                helper.ExecuteSql($@"UPDATE DeviceConfigVersion 
                    SET Status = '{Escape(newStatus)}' 
                    WHERE VersionId = '{Escape(versionId)}';");
            }
            catch (Exception ex)
            {
                throw new Exception($"更新版本状态[{versionId}]失败: {ex.Message}", ex);
            }
        }
 
        private List<DeviceConfigVersion> MapToVersionList(DataTable dt)
        {
            var list = new List<DeviceConfigVersion>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(new DeviceConfigVersion
                {
                    VersionId = row["VersionId"].ToString() ?? string.Empty,
                    Status = Enum.TryParse<VersionStatus>(row["Status"].ToString(), out var st) ? st : VersionStatus.Draft,
                    Hash = row["Hash"].ToString() ?? string.Empty,
                    Author = row["Author"].ToString() ?? string.Empty,
                    Timestamp = DateTime.TryParse(row["Timestamp"].ToString(), out var ts) ? ts : DateTime.Now,
                    DiffDescription = row["DiffDescription"].ToString() ?? string.Empty,
                });
            }
            return list;
        }
 
        #endregion
 
        #region DeviceGroupTemplate CRUD
 
        /// <summary>
        /// 获取所有设备组模板
        /// </summary>
        public List<DeviceGroupTemplate> GetGroupTemplates()
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var dt = helper.GetDataTable("SELECT * FROM DeviceGroupTemplate ORDER BY DeviceType;");
                return MapToTemplateList(dt);
            }
            catch (Exception ex)
            {
                throw new Exception($"获取设备组模板失败: {ex.Message}", ex);
            }
        }
 
        /// <summary>
        /// 保存设备组模板(新增或更新)
        /// </summary>
        public void SaveGroupTemplate(DeviceGroupTemplate template)
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
                var sql = $@"INSERT OR REPLACE INTO DeviceGroupTemplate 
                    (TemplateId, DeviceType, DriverType, Transport, DeviceCount, ChannelsPerDevice, AddressRule, SlotStart) 
                    VALUES (
                        '{Escape(template.TemplateId)}',
                        '{Escape(template.DeviceType)}',
                        '{Escape(template.DriverType)}',
                        '{Escape(template.Transport)}',
                        {template.DeviceCount},
                        {template.ChannelsPerDevice},
                        '{Escape(template.AddressRule)}',
                        {template.SlotStart}
                    );";
                helper.ExecuteSql(sql);
            }
            catch (Exception ex)
            {
                throw new Exception($"保存设备组模板[{template.TemplateId}]失败: {ex.Message}", ex);
            }
        }
 
        private List<DeviceGroupTemplate> MapToTemplateList(DataTable dt)
        {
            var list = new List<DeviceGroupTemplate>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(new DeviceGroupTemplate
                {
                    TemplateId = row["TemplateId"].ToString() ?? string.Empty,
                    DeviceType = row["DeviceType"].ToString() ?? string.Empty,
                    DriverType = row["DriverType"].ToString() ?? string.Empty,
                    Transport = row["Transport"].ToString() ?? string.Empty,
                    DeviceCount = Convert.ToInt32(row["DeviceCount"]),
                    ChannelsPerDevice = Convert.ToInt32(row["ChannelsPerDevice"]),
                    AddressRule = row["AddressRule"].ToString() ?? string.Empty,
                    SlotStart = Convert.ToInt32(row["SlotStart"]),
                });
            }
            return list;
        }
 
        #endregion
 
        #region 冲突检测与哈希计算
 
        /// <summary>
        /// 检测配置冲突:IP冲突、Slot映射重复、通道越界等
        /// </summary>
        /// <returns>冲突描述列表,为空则表示无冲突</returns>
        public List<string> DetectConflicts()
        {
            var conflicts = new List<string>();
            try
            {
                using var helper = new SqliteHelper(_dbPath);
 
                // 1. 检测IP/Endpoint冲突(相同Endpoint的不同设备)
                var ipSql = @"SELECT Endpoint, COUNT(*) as Cnt 
                    FROM DeviceDefinition 
                    WHERE Transport = 'Tcp' 
                    GROUP BY Endpoint 
                    HAVING Cnt > 1;";
                var ipDt = helper.GetDataTable(ipSql);
                foreach (DataRow row in ipDt.Rows)
                {
                    conflicts.Add($"IP/Endpoint冲突: {row["Endpoint"]} 被 {row["Cnt"]} 个设备使用");
                }
 
                // 2. 检测Slot映射重复(相同Slot+Capability+Version映射到不同设备)
                var slotSql = @"SELECT SlotId, Capability, Version, COUNT(*) as Cnt 
                    FROM SlotDeviceMapping 
                    GROUP BY SlotId, Capability, Version 
                    HAVING Cnt > 1;";
                var slotDt = helper.GetDataTable(slotSql);
                foreach (DataRow row in slotDt.Rows)
                {
                    conflicts.Add($"Slot映射重复: Slot={row["SlotId"]}, Capability={row["Capability"]}, Version={row["Version"]}");
                }
 
                // 3. 检测通道越界(MaxVoltage或MaxCurrent为负数)
                var channelSql = @"SELECT DeviceId, ChannelId, MaxVoltage, MaxCurrent 
                    FROM DeviceChannel 
                    WHERE MaxVoltage < 0 OR MaxCurrent < 0;";
                var channelDt = helper.GetDataTable(channelSql);
                foreach (DataRow row in channelDt.Rows)
                {
                    conflicts.Add($"通道参数越界: 设备={row["DeviceId"]}, 通道={row["ChannelId"]}, " +
                                  $"电压={row["MaxVoltage"]}, 电流={row["MaxCurrent"]}");
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"冲突检测失败: {ex.Message}", ex);
            }
 
            return conflicts;
        }
 
        /// <summary>
        /// 计算当前配置的SHA256哈希
        /// </summary>
        /// <returns>SHA256哈希字符串(小写十六进制)</returns>
        public string ComputeConfigHash()
        {
            try
            {
                using var helper = new SqliteHelper(_dbPath);
 
                // 拼接所有设备定义的关键信息作为哈希输入
                var sb = new StringBuilder();
 
                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"]};");
                }
 
                var channelDt = helper.GetDataTable("SELECT * FROM DeviceChannel ORDER BY DeviceId, ChannelId;");
                foreach (DataRow row in channelDt.Rows)
                {
                    sb.Append($"CHANNEL:{row["DeviceId"]}|{row["ChannelId"]}|{row["Capability"]}|{row["MaxVoltage"]}|{row["MaxCurrent"]};");
                }
 
                var slotDt = helper.GetDataTable("SELECT * FROM SlotDeviceMapping ORDER BY SlotId, Capability, Version;");
                foreach (DataRow row in slotDt.Rows)
                {
                    sb.Append($"SLOT:{row["SlotId"]}|{row["Capability"]}|{row["Version"]}|{row["DeviceId"]}|{row["ChannelId"]};");
                }
 
                using var sha256 = SHA256.Create();
                var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
            }
            catch (Exception ex)
            {
                throw new Exception($"计算配置哈希失败: {ex.Message}", ex);
            }
        }
 
        #endregion
 
        #region 辅助方法
 
        /// <summary>
        /// SQL字符串转义(防止注入)
        /// </summary>
        private static string Escape(string value)
        {
            return value?.Replace("'", "''") ?? string.Empty;
        }
 
        #endregion
    }
}