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
{
///
/// 设备配置数据访问层,基于 SQLite 实现
///
public class DeviceConfigModel
{
private string _dbPath = string.Empty;
///
/// 初始化数据库,创建所有表
///
/// 数据库文件路径
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
{
// 设备定义表
@"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
///
/// 获取所有设备定义
///
public List 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);
}
}
///
/// 根据ID获取设备定义
///
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);
}
}
///
/// 保存设备定义(新增或更新)
///
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);
}
}
///
/// 删除设备定义
///
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 MapToDeviceList(DataTable dt)
{
var list = new List();
foreach (DataRow row in dt.Rows)
{
list.Add(new DeviceDefinition
{
DeviceId = row["DeviceId"].ToString() ?? string.Empty,
Kind = Enum.TryParse(row["Kind"].ToString(), out var kind) ? kind : DeviceKind.PowerSupply,
DriverType = row["DriverType"].ToString() ?? string.Empty,
Transport = Enum.TryParse(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
///
/// 获取指定设备的所有通道
///
public List 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);
}
}
///
/// 保存设备通道(新增或更新)
///
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);
}
}
///
/// 删除设备通道
///
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 MapToChannelList(DataTable dt)
{
var list = new List();
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
///
/// 获取所有Slot映射
///
public List 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);
}
}
///
/// 保存Slot映射(新增或更新)
///
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);
}
}
///
/// 删除Slot映射
///
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 MapToSlotMappingList(DataTable dt)
{
var list = new List();
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
///
/// 获取所有配置版本
///
public List 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);
}
}
///
/// 保存配置版本(新增或更新)
///
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);
}
}
///
/// 更新版本状态
///
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 MapToVersionList(DataTable dt)
{
var list = new List();
foreach (DataRow row in dt.Rows)
{
list.Add(new DeviceConfigVersion
{
VersionId = row["VersionId"].ToString() ?? string.Empty,
Status = Enum.TryParse(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
///
/// 获取所有设备组模板
///
public List 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);
}
}
///
/// 保存设备组模板(新增或更新)
///
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 MapToTemplateList(DataTable dt)
{
var list = new List();
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 冲突检测与哈希计算
///
/// 检测配置冲突:IP冲突、Slot映射重复、通道越界等
///
/// 冲突描述列表,为空则表示无冲突
public List DetectConflicts()
{
var conflicts = new List();
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;
}
///
/// 计算当前配置的SHA256哈希
///
/// SHA256哈希字符串(小写十六进制)
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 辅助方法
///
/// SQL字符串转义(防止注入)
///
private static string Escape(string value)
{
return value?.Replace("'", "''") ?? string.Empty;
}
#endregion
}
}