using AIK.DataAccess;
using AIK.Models;
using AIK.Models.ApiModels;
using AIK.Models.ApiModels.Brand;
using AIK.Models.SQLiteModels;
using AIK.Service.IService;
using Dapper;
using HidSharp;
using System.Data.SQLite;
using Microsoft.VisualBasic;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace AIK.Service.Service
{
public class SqliteCacheService : IDataCacheService, IDisposable
{
//private readonly SQLiteConnection _connection;//弃用(原单例模式)
private readonly string _databasePath;
private bool _disposed = false;
public SqliteCacheService()
{
//_connection = SQLiteCore.CreateConnection();//弃用(原单例模式)
_databasePath = SQLiteCore.DatabasePath;
InitializeDatabase();
}
private void InitializeDatabase()
{
using var pooledConn = SQLiteCore.GetConnection();
try
{
var conn = pooledConn.InnerConnection; // 借用连接
#region 创建表
// 创建缓存表
var createTableSql = @"
CREATE TABLE IF NOT EXISTS CacheItems (
CacheKey TEXT PRIMARY KEY,
Data TEXT NOT NULL,
DataType TEXT NOT NULL,
CreatedAt DATETIME NOT NULL,
ExpiresAt DATETIME NOT NULL,
LastAccessed DATETIME NOT NULL,
AccessCount INTEGER DEFAULT 0,
Metadata TEXT
);
CREATE INDEX IF NOT EXISTS IX_CacheItems_ExpiresAt ON CacheItems(ExpiresAt);
CREATE INDEX IF NOT EXISTS IX_CacheItems_LastAccessed ON CacheItems(LastAccessed);
CREATE INDEX IF NOT EXISTS IX_CacheItems_DataType ON CacheItems(DataType);
";
// 3. 创建拆分缓存父表 SplitCache_Parent(无 COMMENT)
var createSplitParentTableSql = @"
CREATE TABLE IF NOT EXISTS SplitCache_Parent (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
CacheKey TEXT NOT NULL,
ParentData TEXT NOT NULL, -- 父级核心数据(无列表)
SubItemCount INTEGER DEFAULT 0, -- 子级数据条数
DataType TEXT NOT NULL, -- 父级数据类型
CreatedAt DATETIME NOT NULL,
ExpiresAt DATETIME NOT NULL,
LastAccessed DATETIME NOT NULL,
AccessCount INTEGER DEFAULT 0,
Metadata TEXT
);";
// 4. 为 SplitCache_Parent 表创建独立索引
var createSplitParentIndexSql = @"
CREATE INDEX IF NOT EXISTS IX_SplitCacheParent_ExpiresAt ON SplitCache_Parent(ExpiresAt);
CREATE INDEX IF NOT EXISTS IX_SplitCacheParent_LastAccessed ON SplitCache_Parent(LastAccessed);";
// 5. 创建拆分缓存子表 SplitCache_ModelList(无 COMMENT,字段含义用代码注释说明)
var createSplitModelListTableSql = @"
CREATE TABLE IF NOT EXISTS SplitCache_ModelList (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ParentTableId INTEGER NOT NULL,
CacheKey TEXT NOT NULL, -- 关联 SplitCache_Parent.CacheKey
ParentID INTEGER NOT NULL, -- 关联 VehModelClass.ID
KeyId INTEGER NOT NULL, -- 对应 VehKeyClass.id
KeyKeyId TEXT NOT NULL, -- 对应 VehKeyClass.keyId
Name TEXT NOT NULL, -- 对应 VehKeyClass.Name
ModelName TEXT NOT NULL, -- 对应 VehKeyClass.modelName
ModelEngName TEXT NOT NULL, -- 对应 VehKeyClass.modelEngName
ModelPuName TEXT NOT NULL, -- 对应 VehKeyClass.modelPuName
ModelXiName TEXT NOT NULL, -- 对应 VehKeyClass.modelXiName
ModelInName TEXT NOT NULL, -- 对应 VehKeyClass.modelInName
Rate TEXT NOT NULL, -- 对应 VehKeyClass.rate(频率)
Usable TEXT NOT NULL, -- 对应 VehKeyClass.usable(子机中文描述)
UsableEn TEXT NOT NULL, -- 对应 VehKeyClass.usableEn(子机英文描述)
UsablePu TEXT NOT NULL, -- 对应 VehKeyClass.usablePu
UsableXi TEXT NOT NULL, -- 对应 VehKeyClass.usableXi
Information TEXT, -- 对应 VehKeyClass.information(参考信息)
PicUrl TEXT NOT NULL, -- 对应 VehKeyClass.picUrl(图片路径)
FirmwareNo TEXT NOT NULL, -- 对应 VehKeyClass.firmwareNo(Bin文件名)
SourceUrl TEXT NOT NULL, -- 对应 VehKeyClass.sourceUrl(Bin文件路径)
SpCode TEXT NOT NULL, -- 对应 VehKeyClass.spCode(烧录提示)
Support TEXT NOT NULL, -- 对应 VehKeyClass.support(支持设备类型)
ChipType TEXT, -- 对应 VehKeyClass.chipType(芯片类型)
CreateTime TEXT NOT NULL, -- 对应 VehKeyClass.createTime(创建时间)
KeyMatchContent TEXT, -- 对应 keyMatch.content(匹配方法中文)
KeyMatchContentEn TEXT, -- 对应 keyMatch.contentEn(匹配方法英文)
KeyMatchContentPu TEXT, -- 对应 keyMatch.contentPu
KeyMatchContentXi TEXT, -- 对应 keyMatch.contentXi
KeyMatchContentIn TEXT, -- 对应 keyMatch.contentIn
IsSelected BOOLEAN NOT NULL DEFAULT 0, -- 对应 VehKeyClass.IsSelected
LocalImagePath TEXT, -- 对应 VehKeyClass.LocalImagePath(本地图片路径)
-- 外键约束:删除父表数据时,自动删除子表关联数据
FOREIGN KEY (ParentTableId) REFERENCES SplitCache_Parent(Id) ON DELETE CASCADE
);";
var createKZInfoListTableSql = @"CREATE TABLE IF NOT EXISTS SplitCache_KzInfoList (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ModelListId INTEGER NOT NULL, -- 关键:指向 SplitCache_ModelList.Id
binAddress TEXT,
binAddressArray TEXT, -- JSON: [""0x1000"", ...]
chipType TEXT,
cloudRemote BOOLEAN NOT NULL DEFAULT 0,
dzxp BOOLEAN NOT NULL DEFAULT 0,
firmwareNo TEXT,
fixedByte BOOLEAN NOT NULL DEFAULT 0,
fixedByteArray TEXT, -- JSON
fixedByteArrays TEXT,
sourceUrl TEXT,
sp BOOLEAN NOT NULL DEFAULT 0,
spCode TEXT,
tsRand BOOLEAN NOT NULL DEFAULT 0,
tsRandStr TEXT,
type TEXT,
updateByte BOOLEAN NOT NULL DEFAULT 0,
usable TEXT,
ysSource TEXT,
ysSourceNo TEXT,
zsId TEXT,
CreateTime TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (ModelListId) REFERENCES SplitCache_ModelList(Id) ON DELETE CASCADE
);";
// 6. 为 SplitCache_ModelList 表创建独立索引
var createSplitModelListIndexSql = @"
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_ParentTableId ON SplitCache_ModelList(ParentTableId); -- 外键关联查询(核心索引)
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_CacheKey ON SplitCache_ModelList(CacheKey); -- 按 CacheKey 批量查询
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_ParentID ON SplitCache_ModelList(ParentID); -- 按业务 ParentID 查询
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_KeyId ON SplitCache_ModelList(KeyId);
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_FirmwareNo ON SplitCache_ModelList(FirmwareNo);
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_Support ON SplitCache_ModelList(Support);
CREATE INDEX IF NOT EXISTS IX_SplitCacheModelList_ChipType ON SplitCache_ModelList(ChipType);";
//6.为 SplitCache_KzInfoList 表创建独立索引
var createSplitKzInfoListIndexSql = @"
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_ModelListId ON SplitCache_KzInfoList(ModelListId); -- 外键关联查询(核心索引)
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_ChipType ON SplitCache_KzInfoList(chipType); -- 按芯片类型筛选
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_FirmwareNo ON SplitCache_KzInfoList(firmwareNo); -- 按固件编号查询
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_SpCode ON SplitCache_KzInfoList(spCode); -- 按子机code快速定位
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_Type ON SplitCache_KzInfoList(type); -- 按所属子机类型分类查询
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_Usable ON SplitCache_KzInfoList(usable); -- 按可用子机描述匹配
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_SourceUrl ON SplitCache_KzInfoList(sourceUrl); -- 按固件下载地址查询(用于去重或校验)
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_ZsId ON SplitCache_KzInfoList(zsId); -- 按遥控再生类型ID查询
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_Sp ON SplitCache_KzInfoList(sp); -- 快速筛选特殊子机
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_CloudRemote ON SplitCache_KzInfoList(cloudRemote); -- 按是否云遥控过滤
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_Dzxp ON SplitCache_KzInfoList(dzxp); -- 按是否电子芯片过滤
CREATE INDEX IF NOT EXISTS IX_SplitCacheKzInfoList_UpdateByte ON SplitCache_KzInfoList(updateByte); -- 按是否需修改bin文件筛选";
// 用户token表
var createUserTokenTableSql = @"
CREATE TABLE IF NOT EXISTS SysUserToken (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Token TEXT NOT NULL,
CreatedAt DATETIME NOT NULL,
ExpiresAt DATETIME NOT NULL
);";
//用户信息表
var createUserInfoTableSql = @"
CREATE TABLE IF NOT EXISTS SysUser (
Id INTEGER PRIMARY KEY,
CreateTime TEXT,
DeptId INTEGER,
Email TEXT,
Image TEXT,
Local TEXT,
NickName TEXT,
Phone TEXT,
Remark TEXT,
Sex TEXT,
UpdateTime TEXT,
UserName TEXT,
UserStatus TEXT,
UserType TEXT,
Uuid TEXT UNIQUE,
WorkerAccount TEXT,
WorkerId INTEGER
);";
#endregion
conn.Execute(createTableSql);
conn.Execute(createSplitParentTableSql);
conn.Execute(createSplitParentIndexSql); // 独立执行索引创建
conn.Execute(createSplitModelListTableSql);
conn.Execute(createSplitModelListIndexSql); // 独立执行索引创建
conn.Execute(createKZInfoListTableSql);
conn.Execute(createSplitKzInfoListIndexSql); // 独立执行索引创建
conn.Execute(createUserTokenTableSql);
conn.Execute(createUserInfoTableSql);
// 清理过期缓存
_ = ClearExpiredAsync();
_ = ClearSplitCacheExpiredAsync(); // 新增拆分缓存的过期清理
}
catch (Exception ex)
{
throw new Exception($"初始化缓存数据库失败: {ex.Message}", ex);
}
}
#region 拆分缓存
#region 拆分缓存-写入(拆分 VehModelClass 到父子表)
///
/// 拆分 VehModelClass 数据(父级+子级列表)到拆分缓存表
///
public async Task SetSplitVehModelDataAsync(string cacheKey, VehModelClass vehModel, TimeSpan expiry)
{
if (vehModel == null || vehModel.modelList == null || !vehModel.modelList.Any())
throw new ArgumentNullException(nameof(vehModel), "VehModelClass 或 modelList 不能为空");
using var pooledConn = SQLiteCore.GetConnection();
var conn = pooledConn.InnerConnection;
// 事务保证原子性(仅操作新表)
using (var transaction = conn.BeginTransaction())
{
try
{
// 1. 序列化父级核心数据(VehModelClass 非列表字段)
var parentData = JsonConvert.SerializeObject(new
{
vehModel.ID,
vehModel.typeName,
vehModel.typeEngName,
vehModel.typePuName,
vehModel.typeXiName,
vehModel.typeInName
}, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.None // 紧凑序列化,减少存储体积
});
// 2. 写入父表 SplitCache_Parent
var splitParent = new SplitCacheParent
{
CacheKey = cacheKey,
ParentData = parentData,
SubItemCount = vehModel.modelList.Count,
DataType = typeof(VehModelClass).FullName,
CreatedAt = DateTime.Now,
ExpiresAt = DateTime.Now.Add(expiry),
LastAccessed = DateTime.Now,
AccessCount = 0,
Metadata = JsonConvert.SerializeObject(new
{
VehModelID = vehModel.ID,
ParentDataSize = Encoding.UTF8.GetByteCount(parentData),
SubItemCount = vehModel.modelList.Count,
CacheExpiryMinutes = expiry.TotalMinutes
})
};
var insertParentSql = @"
INSERT INTO SplitCache_Parent (
CacheKey, ParentData, SubItemCount, DataType, CreatedAt, ExpiresAt, LastAccessed, AccessCount, Metadata
) VALUES (
@CacheKey, @ParentData, @SubItemCount, @DataType, @CreatedAt, @ExpiresAt, @LastAccessed, @AccessCount, @Metadata
)";
await conn.ExecuteAsync(insertParentSql, splitParent, transaction);
var parentTableId = await conn.ExecuteScalarAsync(
"SELECT last_insert_rowid()",
transaction: transaction);
// 2. 插入 ModelList 并收集 KzInfo 待插入
var allKzInfoItems = new List();
foreach (var keyItem in vehModel.modelList)
{
// 插入单条 ModelList
var modelListItem = new
{
ParentTableId = (int)parentTableId,
CacheKey = cacheKey,
ParentID = vehModel.ID,
KeyId = keyItem.id,
KeyKeyId = keyItem.keyId ?? string.Empty,
Name = keyItem.Name ?? string.Empty,
ModelName = keyItem.modelName ?? string.Empty,
ModelEngName = keyItem.modelEngName ?? string.Empty,
ModelPuName = keyItem.modelPuName ?? string.Empty,
ModelXiName = keyItem.modelXiName ?? string.Empty,
ModelInName = keyItem.modelInName ?? string.Empty,
Rate = keyItem.rate ?? string.Empty,
Usable = keyItem.usable ?? string.Empty,
UsableEn = keyItem.usableEn ?? string.Empty,
UsablePu = keyItem.usablePu ?? string.Empty,
UsableXi = keyItem.usableXi ?? string.Empty,
Information = keyItem.information ?? string.Empty,
PicUrl = keyItem.picUrl ?? string.Empty,
FirmwareNo = keyItem.firmwareNo ?? string.Empty,
SourceUrl = keyItem.sourceUrl ?? string.Empty,
SpCode = keyItem.spCode ?? string.Empty,
Support = keyItem.support ?? string.Empty,
ChipType = keyItem.chipType ?? string.Empty,
CreateTime = keyItem.createTime ?? string.Empty,
KeyMatchContent = keyItem.keyMatch?.content ?? string.Empty,
KeyMatchContentEn = keyItem.keyMatch?.contentEn ?? string.Empty,
KeyMatchContentPu = keyItem.keyMatch?.contentPu ?? string.Empty,
KeyMatchContentXi = keyItem.keyMatch?.contentXi ?? string.Empty,
KeyMatchContentIn = keyItem.keyMatch?.contentIn ?? string.Empty,
IsSelected = keyItem.IsSelected,
LocalImagePath = keyItem.LocalImagePath ?? string.Empty
};
await conn.ExecuteAsync(@"
INSERT INTO SplitCache_ModelList (
ParentTableId, CacheKey, ParentID, KeyId, KeyKeyId, Name, ModelName, ModelEngName, ModelPuName, ModelXiName, ModelInName,
Rate, Usable, UsableEn, UsablePu, UsableXi, Information, PicUrl, FirmwareNo, SourceUrl, SpCode,
Support, ChipType, CreateTime, KeyMatchContent, KeyMatchContentEn, KeyMatchContentPu,
KeyMatchContentXi, KeyMatchContentIn, IsSelected, LocalImagePath
) VALUES (
@ParentTableId, @CacheKey, @ParentID, @KeyId, @KeyKeyId, @Name, @ModelName, @ModelEngName, @ModelPuName, @ModelXiName, @ModelInName,
@Rate, @Usable, @UsableEn, @UsablePu, @UsableXi, @Information, @PicUrl, @FirmwareNo, @SourceUrl, @SpCode,
@Support, @ChipType, @CreateTime, @KeyMatchContent, @KeyMatchContentEn, @KeyMatchContentPu,
@KeyMatchContentXi, @KeyMatchContentIn, @IsSelected, @LocalImagePath
)", modelListItem, transaction);
var modelListId = await conn.ExecuteScalarAsync("SELECT last_insert_rowid()", transaction: transaction);
// 3. 收集该 keyItem 下的所有 KzInfo
if (keyItem.kzInfoList != null && keyItem.kzInfoList.Any())
{
foreach (var kz in keyItem.kzInfoList)
{
allKzInfoItems.Add(new SplitCacheKzInfoListItem
{
ModelListId = (int)modelListId, // 关键:关联到刚插入的 ModelList 记录
binAddress = kz.binAddress ?? string.Empty,
binAddressArray = kz.binAddressArray != null ? JsonConvert.SerializeObject(kz.binAddressArray) : "[]",
chipType = kz.chipType ?? string.Empty,
cloudRemote = kz.cloudRemote,
dzxp = kz.dzxp,
firmwareNo = kz.firmwareNo ?? string.Empty,
fixedByte = kz.fixedByte,
fixedByteArray = kz.fixedByteArray != null ? JsonConvert.SerializeObject(kz.fixedByteArray) : "[]",
fixedByteArrays = kz.fixedByteArrays ?? string.Empty,
sourceUrl = kz.sourceUrl ?? string.Empty,
sp = kz.sp,
spCode = kz.spCode ?? string.Empty,
tsRand = kz.tsRand,
tsRandStr = kz.tsRandStr ?? string.Empty,
type = kz.type ?? string.Empty,
updateByte = kz.updateByte,
usable = kz.usable ?? string.Empty,
ysSource = kz.ysSource ?? string.Empty,
ysSourceNo = kz.ysSourceNo ?? string.Empty,
zsId = kz.zsId ?? string.Empty,
CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
});
}
}
}
// 4. 批量插入所有 KzInfo
if (allKzInfoItems.Any())
{
await conn.ExecuteAsync(@"
INSERT INTO SplitCache_KzInfoList (
ModelListId, binAddress, binAddressArray, chipType, cloudRemote, dzxp, firmwareNo,
fixedByte, fixedByteArray, fixedByteArrays, sourceUrl, sp, spCode, tsRand, tsRandStr,
type, updateByte, usable, ysSource, ysSourceNo, zsId, CreateTime
) VALUES (
@ModelListId, @binAddress, @binAddressArray, @chipType, @cloudRemote, @dzxp, @firmwareNo,
@fixedByte, @fixedByteArray, @fixedByteArrays, @sourceUrl, @sp, @spCode, @tsRand, @tsRandStr,
@type, @updateByte, @usable, @ysSource, @ysSourceNo, @zsId, @CreateTime
)", allKzInfoItems, transaction);
}
// 提交事务
transaction.Commit();
//System.Diagnostics.Debug.WriteLine($"拆分缓存写入成功:CacheKey={cacheKey},父级ID={vehModel.ID},子级条数={vehModel.modelList.Count}");
}
catch (Exception ex)
{
transaction.Rollback();
//System.Diagnostics.Debug.WriteLine($"写入拆分缓存失败:{ex.Message}");
throw new Exception($"写入拆分缓存失败(CacheKey={cacheKey})", ex);
}
}
}
#endregion
#region 拆分缓存-查询(合并父子表为 VehModelClass)
///
/// 查询拆分缓存,返回完整的 List 实体
///
public async Task> GetSplitVehModelDataAsync(string cacheKey)
{
if (string.IsNullOrEmpty(cacheKey))
throw new ArgumentNullException(nameof(cacheKey), "缓存Key不能为空");
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
try
{
// 1. 查父表,验证缓存是否存在且未过期
var getParentSql = @"
SELECT * FROM SplitCache_Parent
WHERE CacheKey = @CacheKey AND ExpiresAt > @Now";
var splitParents = await _connection.QueryAsync(getParentSql, new
{
CacheKey = cacheKey,
Now = DateTime.Now
});
var splitParentList = splitParents.ToList();
if (splitParentList.Count == 0)
{
//System.Diagnostics.Debug.WriteLine($"拆分缓存未命中:CacheKey={cacheKey}");
return null;
}
// 2. 更新父表访问统计
await UpdateSplitParentAccessStatsAsync(cacheKey);
// 3. 查子表(关联 CacheKey,获取所有 VehKeyClass 对应的记录)
var getChildSql = "SELECT * FROM SplitCache_ModelList WHERE CacheKey = @CacheKey";
var modelListItems = (await _connection.QueryAsync(getChildSql, new
{
CacheKey = cacheKey
})).ToList();
var result = new List();
// 4.批量查询所有关联的 KzInfo(通过 ModelList.Id)
var modelListIds = modelListItems.Select(m => m.Id).ToArray();
var getKzInfoSql = $"SELECT * FROM SplitCache_KzInfoList WHERE ModelListId IN ({string.Join(",", modelListIds)})";
var kzInfoItems = (await _connection.QueryAsync(getKzInfoSql)).ToList();
// 按 ModelListId 分组,便于快速查找
var kzInfoLookup = kzInfoItems
.GroupBy(k => k.ModelListId)
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var splitParent in splitParentList)
{
// 4. 校验子级数据完整性
if (modelListItems.Count != splitParent.SubItemCount)
{
var errorMsg = $"拆分缓存子级数据不完整:CacheKey={cacheKey},预期 {splitParent.SubItemCount} 条,实际 {modelListItems.Count} 条";
//System.Diagnostics.Debug.WriteLine(errorMsg);
await DeleteSplitVehModelDataAsync(cacheKey); // 删除无效缓存
return null;
}
// 5. 反序列化父级数据(VehModelClass 核心字段)
var parentCoreData = JsonConvert.DeserializeObject(splitParent.ParentData);
var vehModel = new VehModelClass
{
ID = parentCoreData?.ID ?? 0,
typeName = parentCoreData?.typeName ?? string.Empty,
typeEngName = parentCoreData?.typeEngName ?? string.Empty,
typePuName = parentCoreData?.typePuName ?? string.Empty,
typeXiName = parentCoreData?.typeXiName ?? string.Empty,
typeInName = parentCoreData?.typeInName ?? string.Empty,
modelList = new List()
};
// 6. 转换子表数据为 VehKeyClass 实体(含 keyMatch 嵌套类)
foreach (var item in modelListItems)
{
var vehKey = new VehKeyClass
{
parentId = item.ParentID,
id = item.KeyId,
keyId = item.KeyKeyId,
Name = item.Name,
modelName = item.ModelName,
modelEngName = item.ModelEngName,
modelPuName = item.ModelPuName,
modelXiName = item.ModelXiName,
modelInName = item.ModelInName,
rate = item.Rate,
usable = item.Usable,
usableEn = item.UsableEn,
usablePu = item.UsablePu,
usableXi = item.UsableXi,
information = item.Information,
picUrl = item.PicUrl,
firmwareNo = item.FirmwareNo,
sourceUrl = item.SourceUrl,
spCode = item.SpCode,
support = item.Support,
chipType = item.ChipType,
createTime = item.CreateTime,
// 重组 keyMatch 嵌套类
keyMatch = new keyMatch
{
content = item.KeyMatchContent,
contentEn = item.KeyMatchContentEn,
contentPu = item.KeyMatchContentPu,
contentXi = item.KeyMatchContentXi,
contentIn = item.KeyMatchContentIn
},
// UI 相关字段(BitmapImage 需业务层自行加载,此处仅设置 LocalImagePath)
LocalImagePath = item.LocalImagePath,
IsSelected = item.IsSelected,
kzInfoList = new List() // 初始化
};
// 挂载对应的 KzInfo 列表
if (kzInfoLookup.TryGetValue(item.Id, out var kzList))
{
foreach (var kzItem in kzList)
{
vehKey.kzInfoList.Add(new KzInfo
{
binAddress = kzItem.binAddress,
binAddressArray = !string.IsNullOrEmpty(kzItem.binAddressArray)
? JsonConvert.DeserializeObject>(kzItem.binAddressArray)
: new List(),
chipType = kzItem.chipType,
cloudRemote = kzItem.cloudRemote,
dzxp = kzItem.dzxp,
firmwareNo = kzItem.firmwareNo,
fixedByte = kzItem.fixedByte,
fixedByteArray = !string.IsNullOrEmpty(kzItem.fixedByteArray)
? JsonConvert.DeserializeObject>(kzItem.fixedByteArray)
: new List(),
fixedByteArrays = kzItem.fixedByteArrays,
sourceUrl = kzItem.sourceUrl,
sp = kzItem.sp,
spCode = kzItem.spCode,
tsRand = kzItem.tsRand,
tsRandStr = kzItem.tsRandStr,
type = kzItem.type,
updateByte = kzItem.updateByte,
usable = kzItem.usable,
ysSource = kzItem.ysSource,
ysSourceNo = kzItem.ysSourceNo,
zsId = kzItem.zsId
});
}
}
vehModel.modelList.Add(vehKey);
}
result.Add(vehModel);
}
return result;
}
catch (Exception ex)
{
//System.Diagnostics.Debug.WriteLine($"查询拆分缓存失败:{ex.Message}");
return null;
}
}
#endregion
#region 拆分缓存-删除(同步删除父子表数据)
///
/// 删除指定 CacheKey 的拆分缓存(父子表同步删除)
///
public async Task DeleteSplitVehModelDataAsync(string cacheKey)
{
if (string.IsNullOrEmpty(cacheKey))
throw new ArgumentNullException(nameof(cacheKey), "缓存Key不能为空");
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
using (var transaction = _connection.BeginTransaction())
{
try
{
// 1. 先删除子表(外键约束 ON DELETE CASCADE 也会自动删除,但手动删除更清晰)
var deleteChildCount = await _connection.ExecuteAsync(
"DELETE FROM SplitCache_ModelList WHERE CacheKey = @CacheKey",
new { CacheKey = cacheKey },
transaction
);
// 2. 再删除父表
var deleteParentCount = await _connection.ExecuteAsync(
"DELETE FROM SplitCache_Parent WHERE CacheKey = @CacheKey",
new { CacheKey = cacheKey },
transaction
);
transaction.Commit();
System.Diagnostics.Debug.WriteLine($"拆分缓存删除成功:CacheKey={cacheKey},删除子级{deleteChildCount}条,父级{deleteParentCount}条");
}
catch (Exception ex)
{
transaction.Rollback();
System.Diagnostics.Debug.WriteLine($"删除拆分缓存失败:{ex.Message}");
throw new Exception($"删除拆分缓存失败(CacheKey={cacheKey})", ex);
}
}
}
#endregion
#region 拆分缓存-过期清理(独立清理,不影响旧缓存)
///
/// 清理拆分缓存中过期的数据(父子表同步清理)
///
public async Task ClearSplitCacheExpiredAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
using (var transaction = _connection.BeginTransaction())
{
try
{
// 1. 查询所有过期的父表 CacheKey
var expiredKeysSql = "SELECT CacheKey FROM SplitCache_Parent WHERE ExpiresAt <= @Now";
var expiredKeys = (await _connection.QueryAsync(expiredKeysSql, new { Now = DateTime.Now }, transaction)).ToList();
if (!expiredKeys.Any())
{
transaction.Commit();
//System.Diagnostics.Debug.WriteLine("无过期拆分缓存数据");
return;
}
// 2. 批量删除过期子表数据
var deleteChildCount = await _connection.ExecuteAsync(
"DELETE FROM SplitCache_ModelList WHERE CacheKey IN @ExpiredKeys",
new { ExpiredKeys = expiredKeys },
transaction
);
// 3. 批量删除过期父表数据
var deleteParentCount = await _connection.ExecuteAsync(
"DELETE FROM SplitCache_Parent WHERE CacheKey IN @ExpiredKeys",
new { ExpiredKeys = expiredKeys },
transaction
);
transaction.Commit();
//System.Diagnostics.Debug.WriteLine($"清理过期拆分缓存成功:共{expiredKeys.Count}组,删除子级{deleteChildCount}条,父级{deleteParentCount}条");
}
catch (Exception ex)
{
transaction.Rollback();
//System.Diagnostics.Debug.WriteLine($"清理过期拆分缓存失败:{ex.Message}");
}
}
}
#endregion
#region 拆分缓存-辅助方法
///
/// 更新拆分缓存父表的访问统计
///
private async Task UpdateSplitParentAccessStatsAsync(string cacheKey)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = @"
UPDATE SplitCache_Parent
SET LastAccessed = @LastAccessed, AccessCount = AccessCount + 1
WHERE CacheKey = @CacheKey";
await _connection.ExecuteAsync(sql, new
{
CacheKey = cacheKey,
LastAccessed = DateTime.Now
});
}
///
/// 检查拆分缓存是否存在(未过期)
///
public async Task SplitCacheExistsAsync(string cacheKey)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = "SELECT 1 FROM SplitCache_Parent WHERE CacheKey = @CacheKey AND ExpiresAt > @Now";
var result = await _connection.ExecuteScalarAsync(sql, new
{
CacheKey = cacheKey,
Now = DateTime.Now
});
return result.HasValue;
}
#endregion
#endregion
#region 缓存基础方法
public async Task GetOrCreateAsync(string key, Func> dataFetchFunc, TimeSpan expiry) where T : class
{
// 先尝试从缓存获取
var cachedData = await GetAsync(key);
if (cachedData != null)
{
if (cachedData switch
{
ICollection { Count: > 0 } => true,
Array { Length: > 0 } => true,
string { Length: > 0 } => true,
_ => false
})
{
return cachedData;
}
//return cachedData;
}
// 缓存未命中,从数据源获取
var freshData = await dataFetchFunc();
if (freshData != null)
{
// 异步保存到缓存(不阻塞当前操作)
_ = SetAsync(key, freshData, expiry);
}
return freshData;
}
public async Task GetAsync(string key) where T : class
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
try
{
var sql = @"
SELECT * FROM CacheItems
WHERE CacheKey = @Key AND ExpiresAt > @Now";
var cacheItem = await _connection.QueryFirstOrDefaultAsync(sql, new
{
Key = key,
Now = DateTime.Now
});
if (cacheItem != null)
{
// 更新访问统计
await UpdateAccessStatsAsync(key);
// 反序列化数据
return DeserializeData(cacheItem.Data);
}
return null;
}
catch (Exception ex)
{
//System.Diagnostics.Debug.WriteLine($"缓存读取失败: {ex.Message}");
return null;
}
}
public async Task SetAsync(string key, T data, TimeSpan expiry) where T : class
{
if (data == null) return;
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
try
{
var cacheItem = new CacheItem
{
CacheKey = key,
Data = SerializeData(data),
DataType = typeof(T).FullName,
CreatedAt = DateTime.Now,
ExpiresAt = DateTime.Now.Add(expiry),
LastAccessed = DateTime.Now,
AccessCount = 0,
Metadata = JsonConvert.SerializeObject(new
{
DataSize = GetDataSize(data),
ItemCount = data is System.Collections.ICollection collection ? collection.Count : 0
})
};
var sql = @"
INSERT OR REPLACE INTO CacheItems
(CacheKey, Data, DataType, CreatedAt, ExpiresAt, LastAccessed, AccessCount, Metadata)
VALUES
(@CacheKey, @Data, @DataType, @CreatedAt, @ExpiresAt, @LastAccessed, @AccessCount, @Metadata)";
await _connection.ExecuteAsync(sql, cacheItem);
}
catch (Exception ex)
{
//System.Diagnostics.Debug.WriteLine($"缓存保存失败: {ex.Message}");
}
}
public async Task ExistsAsync(string key)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = "SELECT 1 FROM CacheItems WHERE CacheKey = @Key AND ExpiresAt > @Now";
var result = await _connection.ExecuteScalarAsync(sql, new
{
Key = key,
Now = DateTime.Now
});
return result.HasValue;
}
public async Task RemoveAsync(string key)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
await _connection.ExecuteAsync("DELETE FROM CacheItems WHERE CacheKey = @Key", new { Key = key });
}
public async Task ClearExpiredAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
await _connection.ExecuteAsync("DELETE FROM CacheItems WHERE ExpiresAt <= @Now", new { Now = DateTime.Now });
}
public async Task ClearAllAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
await _connection.ExecuteAsync("DELETE FROM CacheItems");
await _connection.ExecuteAsync("DELETE FROM SplitCache_Parent");
await _connection.ExecuteAsync("DELETE FROM SplitCache_ModelList");
}
public async Task> GetCacheStatsAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
return await _connection.QueryAsync(
"SELECT * FROM CacheItems ORDER BY LastAccessed DESC");
}
public async Task GetCacheSizeAsync()
{
try
{
var fileInfo = new FileInfo(_databasePath);
return fileInfo.Exists ? fileInfo.Length : 0;
}
catch
{
return 0;
}
}
private async Task UpdateAccessStatsAsync(string key)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = @"
UPDATE CacheItems
SET LastAccessed = @LastAccessed, AccessCount = AccessCount + 1
WHERE CacheKey = @Key";
await _connection.ExecuteAsync(sql, new
{
Key = key,
LastAccessed = DateTime.Now
});
}
private string SerializeData(T data)
{
return JsonConvert.SerializeObject(data, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
});
}
private T DeserializeData(string data)
{
return JsonConvert.DeserializeObject(data, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
}
private long GetDataSize(T data)
{
try
{
var json = SerializeData(data);
return System.Text.Encoding.UTF8.GetByteCount(json);
}
catch
{
return 0;
}
}
public void Dispose()
{
if (!_disposed)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
_connection?.Close();
_connection?.Dispose();
_disposed = true;
}
}
#endregion
#region 用户Token
public async Task StoreUserTokenAsync(string token, TimeSpan? expiry = null)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var createdAt = DateTime.Now;
DateTime? expiresAt = DateTime.Now.AddYears(10);
if (expiry.HasValue)
{
expiresAt = createdAt.Add(expiry.Value);
}
var sql = @"
INSERT INTO SysUserToken (Token, CreatedAt, ExpiresAt)
VALUES (@Token, @CreatedAt, @ExpiresAt)";
await _connection.ExecuteAsync(sql, new
{
Token = token,
CreatedAt = createdAt,
ExpiresAt = expiresAt
});
}
public async Task IsUserTokenValidAsync(string token)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = @"
SELECT 1 FROM SysUserToken
WHERE Token = @Token AND ( ExpiresAt > @Now)";
var result = await _connection.ExecuteScalarAsync(sql, new
{
Token = token,
Now = DateTime.Now
});
return result.HasValue;
}
public async Task ClearUserTokenAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = "DELETE FROM SysUserToken ";
await _connection.ExecuteAsync(sql);
}
public async Task ClearExpiredUserTokensAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = "DELETE FROM SysUserToken WHERE ExpiresAt <= @Now";
await _connection.ExecuteAsync(sql, new { Now = DateTime.Now });
}
public async Task GetLatestUserTokenAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = @"
SELECT Token FROM SysUserToken
WHERE ExpiresAt > @Now
ORDER BY CreatedAt DESC LIMIT 1";
return await _connection.ExecuteScalarAsync(sql, new { Now = DateTime.Now });
}
public async Task StoreUserInfoAsync(UserModel user)
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = @"
INSERT OR REPLACE INTO SysUser
(Id, CreateTime, DeptId, Email, Image, Local, NickName, Phone, Remark,
Sex, UpdateTime, UserName, UserStatus, UserType, Uuid, WorkerAccount, WorkerId)
VALUES
(@Id, @CreateTime, @DeptId, @Email, @Image, @Local, @NickName, @Phone, @Remark,
@Sex, @UpdateTime, @UserName, @UserStatus, @UserType, @Uuid, @WorkerAccount, @WorkerId)";
var rowsAffected = await _connection.ExecuteAsync(sql, user);
return rowsAffected > 0;
}
public async Task ClearUserInfoAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = "DELETE FROM SysUser";
await _connection.ExecuteAsync(sql);
}
public async Task GetUserInfoAsync()
{
using var pooledConn = SQLiteCore.GetConnection();
var _connection = pooledConn.InnerConnection;
var sql = @"
SELECT * FROM SysUser LIMIT 1";
return await _connection.QueryFirstOrDefaultAsync(sql);
}
#endregion
}
}