using DeviceService.Core;
using DeviceService.Logging;
using System.Data.SQLite;
using System.IO;
namespace DeviceService.Device
{
/// 设备记录(Device 表的一行)。
public sealed class DeviceRecord
{
/// 自增主键,对应设备实例的 Id;0 表示尚未入库。
public long Id { get; set; }
/// 设备业务标识(唯一),对应设备实例的 DeviceId。
public string DeviceId { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public string Brand { get; set; } = string.Empty;
public string ConnectStr { get; set; } = string.Empty;
public AccessType AccessType { get; set; }
}
///
/// 设备信息 SQLite 持久化:数据库文件位于 应用根目录\Config\Devices.db。
///
/// 表结构:
/// - Device:Id 为自增主键,DeviceId 为唯一业务标识,Type 用于定位设备实现类
///
/// 所有操作失败仅通过 log 上报,不会中断设备加载流程。
///
public sealed class DeviceDatabase
{
private const string CreateTableSql = @"
CREATE TABLE IF NOT EXISTS Device (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
DeviceId TEXT NOT NULL,
Type TEXT NOT NULL,
Brand TEXT,
ConnectStr TEXT,
AccessType INTEGER NOT NULL DEFAULT 0
);";
private static readonly object _staticSync = new object();
private static DeviceDatabase? _instance;
public static DeviceDatabase Instance
{
get
{
lock (_staticSync)
{
return _instance ??= new DeviceDatabase();
}
}
}
/// 配置目录:应用根目录\Config。
public static string ConfigDirectory =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config");
/// 数据库文件路径。
public static string DatabasePath =>
Path.Combine(ConfigDirectory, "Devices.db");
private readonly object _sync = new object();
private bool _initialized;
private DeviceDatabase()
{
}
/// 确保 Config 目录与库表结构存在。失败返回 false 并通过 log 上报。
private bool EnsureReady()
{
if (_initialized)
{
return true;
}
try
{
if (!Directory.Exists(ConfigDirectory))
{
Directory.CreateDirectory(ConfigDirectory);
}
using (var conn = Open())
{
ExecuteNonQuery(conn, CreateTableSql, null);
// 旧库的结构是 DeviceId 主键,升级为 Id 自增主键
MigrateToAutoIncrementId(conn);
ExecuteNonQuery(conn,
"CREATE UNIQUE INDEX IF NOT EXISTS IX_Device_DeviceId ON Device(DeviceId);",
null);
}
_initialized = true;
return true;
}
catch (Exception ex)
{
GlobalLogger.Logger.Error("DeviceManager", $"[设备库] 初始化失败({DatabasePath}): {ex.Message}");
return false;
}
}
private SQLiteConnection Open()
{
var conn = new SQLiteConnection($"Data Source={DatabasePath};Version=3;");
conn.Open();
return conn;
}
/// 读取库中全部设备记录(按 Type、DeviceId 排序)。
public IReadOnlyList LoadAll()
{
var result = new List();
if (!EnsureReady())
{
return result;
}
try
{
lock (_sync)
{
using (var conn = Open())
using (var cmd = conn.CreateCommand())
{
cmd.CommandText =
"SELECT * FROM Device ORDER BY Type, DeviceId";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(new DeviceRecord
{
Id = GetInt64(reader, "Id"),
DeviceId = GetString(reader, "DeviceId"),
Type = GetString(reader, "Type"),
Brand = GetString(reader, "Brand"),
ConnectStr = GetString(reader, "ConnectStr"),
AccessType = (AccessType)GetInt32(reader, "AccessType")
});
}
}
}
}
}
catch (Exception ex)
{
GlobalLogger.Logger.Error("DeviceManager", $"[设备库] 读取设备列表失败: {ex.Message}");
}
return result;
}
///
/// 写入(覆盖)一台设备的记录,返回其自增主键 Id;失败返回 0。
/// Id 为 0 时新增,否则按 Id 更新。
///
public long Save(DeviceRecord record, Action? log = null)
{
if (record == null || string.IsNullOrWhiteSpace(record.DeviceId))
{
return 0;
}
if (!EnsureReady())
{
return 0;
}
try
{
lock (_sync)
{
using (var conn = Open())
{
if (record.Id > 0)
{
int affected;
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = @"
UPDATE Device SET DeviceId = @id, Type = @t, Brand = @b, ConnectStr = @c, AccessType = @a
WHERE Id = @pk";
cmd.Parameters.AddWithValue("@id", record.DeviceId);
cmd.Parameters.AddWithValue("@t", record.Type ?? string.Empty);
cmd.Parameters.AddWithValue("@b", record.Brand ?? string.Empty);
cmd.Parameters.AddWithValue("@c", record.ConnectStr ?? string.Empty);
cmd.Parameters.AddWithValue("@a", (int)record.AccessType);
cmd.Parameters.AddWithValue("@pk", record.Id);
affected = cmd.ExecuteNonQuery();
}
if (affected > 0)
{
return record.Id;
}
}
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = @"
INSERT INTO Device(DeviceId, Type, Brand, ConnectStr, AccessType)
VALUES(@id, @t, @b, @c, @a)";
cmd.Parameters.AddWithValue("@id", record.DeviceId);
cmd.Parameters.AddWithValue("@t", record.Type ?? string.Empty);
cmd.Parameters.AddWithValue("@b", record.Brand ?? string.Empty);
cmd.Parameters.AddWithValue("@c", record.ConnectStr ?? string.Empty);
cmd.Parameters.AddWithValue("@a", (int)record.AccessType);
cmd.ExecuteNonQuery();
}
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT last_insert_rowid()";
return Convert.ToInt64(cmd.ExecuteScalar());
}
}
}
}
catch (Exception ex)
{
log?.Invoke($"[设备库] 保存 {record.DeviceId} 失败: {ex.Message}");
return 0;
}
}
/// 删除一台设备的记录。
public void Remove(string deviceId)
{
if (string.IsNullOrEmpty(deviceId) || !EnsureReady())
{
return;
}
try
{
lock (_sync)
{
using (var conn = Open())
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "DELETE FROM Device WHERE DeviceId = @id";
cmd.Parameters.AddWithValue("@id", deviceId);
cmd.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
GlobalLogger.Logger.Error("DeviceManager", $"[设备库] 移除 {deviceId} 失败: {ex.Message}");
}
}
/// 旧库(DeviceId 作主键)升级为 Id 自增主键。
private static void MigrateToAutoIncrementId(SQLiteConnection conn)
{
var columns = new List();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "PRAGMA table_info(Device)";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
columns.Add(GetString(reader, "name"));
}
}
}
if (columns.Count == 0 || columns.Contains("Id"))
{
return;
}
lock (_staticSync)
{
using (var tx = conn.BeginTransaction())
{
ExecuteNonQuery(conn, "DROP TABLE IF EXISTS Device_old;", tx);
ExecuteNonQuery(conn, "ALTER TABLE Device RENAME TO Device_old;", tx);
ExecuteNonQuery(conn, CreateTableSql, tx);
ExecuteNonQuery(conn, @"
INSERT INTO Device(DeviceId, Type, Brand, ConnectStr, AccessType)
SELECT DeviceId, Type, Brand, ConnectStr, AccessType FROM Device_old;", tx);
ExecuteNonQuery(conn, "DROP TABLE Device_old;", tx);
tx.Commit();
}
}
}
private static void ExecuteNonQuery(SQLiteConnection conn, string sql, SQLiteTransaction? tx)
{
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tx;
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
private static string GetString(SQLiteDataReader reader, string column)
{
object value = reader[column];
return value == DBNull.Value ? string.Empty : value.ToString() ?? string.Empty;
}
private static int GetInt32(SQLiteDataReader reader, string column)
{
object value = reader[column];
return value == DBNull.Value ? 0 : Convert.ToInt32(value);
}
private static long GetInt64(SQLiteDataReader reader, string column)
{
object value = reader[column];
return value == DBNull.Value ? 0L : Convert.ToInt64(value);
}
}
}