mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-23 03:50:30 +08:00
862 lines
35 KiB
C#
862 lines
35 KiB
C#
//------------------------------------------------------------------------------
|
||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||
// 使用文档:https://thingsgateway.cn/
|
||
// QQ群:605534569
|
||
//------------------------------------------------------------------------------
|
||
|
||
using BootstrapBlazor.Components;
|
||
|
||
using Microsoft.CodeAnalysis;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
using System.Collections.Concurrent;
|
||
using System.Data;
|
||
using System.Reflection;
|
||
using System.Runtime.Loader;
|
||
|
||
using ThingsGateway.Extension.Generic;
|
||
using ThingsGateway.NewLife;
|
||
|
||
using TouchSocket.Core;
|
||
|
||
namespace ThingsGateway.Gateway.Application;
|
||
|
||
/// <summary>
|
||
/// 驱动插件服务
|
||
/// </summary>
|
||
internal sealed class PluginService : IPluginService
|
||
{
|
||
/// <summary>
|
||
/// 插件驱动文件夹名称
|
||
/// </summary>
|
||
public const string DirName = "GatewayPlugins";
|
||
|
||
private const string CacheKeyGetPluginOutputs = $"ThingsGateway.Gateway.Application.{nameof(PluginService)}GetList";
|
||
private const string SaveEx = ".save";
|
||
private const string DelEx = ".del";
|
||
|
||
private readonly IDispatchService<PluginInfo> _dispatchService;
|
||
private readonly WaitLock _locker = new(nameof(PluginService));
|
||
private readonly ILogger _logger;
|
||
|
||
public PluginService(ILogger<PluginService> logger, IDispatchService<PluginInfo> dispatchService)
|
||
{
|
||
_logger = logger;
|
||
|
||
_dispatchService = dispatchService;
|
||
//创建插件文件夹
|
||
Directory.CreateDirectory(AppContext.BaseDirectory.CombinePathWithOs(PluginService.DirName));
|
||
//主程序上下文驱动类字典
|
||
_defaultDriverBaseDict = new(App.EffectiveTypes
|
||
.Where(x => (typeof(CollectBase).IsAssignableFrom(x) || typeof(BusinessBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract)
|
||
.ToDictionary(a => $"{Path.GetFileNameWithoutExtension(new FileInfo(a.Assembly.Location).Name)}.{a.Name}"));
|
||
|
||
DeleteBackup(DirName);
|
||
DeleteBackup(AppContext.BaseDirectory);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 插件文件名称/插件域
|
||
/// </summary>
|
||
private ConcurrentDictionary<string, (AssemblyLoadContext AssemblyLoadContext, Assembly Assembly)> _assemblyLoadContextDict { get; } = new();
|
||
|
||
/// <summary>
|
||
/// 主程序上下文中的插件FullName/插件Type
|
||
/// </summary>
|
||
private System.Collections.ObjectModel.ReadOnlyDictionary<string, Type> _defaultDriverBaseDict { get; }
|
||
|
||
|
||
/// <summary>
|
||
/// 插件FullName/插件Type
|
||
/// </summary>
|
||
private ConcurrentDictionary<string, Type> _driverBaseDict { get; } = new();
|
||
|
||
#region public
|
||
|
||
public Type GetDebugUI(string pluginName)
|
||
{
|
||
var driver = GetDriver(pluginName);
|
||
return driver?.DriverDebugUIType;
|
||
}
|
||
public Type GetAddressUI(string pluginName)
|
||
{
|
||
var driver = GetDriver(pluginName);
|
||
return driver?.DriverVariableAddressUIType;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据插件名称获取对应的驱动程序。
|
||
/// </summary>
|
||
/// <param name="pluginName">插件名称,格式为 主插件程序集文件名称.类型名称</param>
|
||
/// <returns>获取到的驱动程序</returns>
|
||
public DriverBase GetDriver(string pluginName)
|
||
{
|
||
try
|
||
{
|
||
// 等待锁的释放,确保线程安全
|
||
_locker.Wait();
|
||
|
||
// 解析插件名称,获取文件名和类型名
|
||
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(pluginName);
|
||
|
||
// 如果是默认键,则搜索主程序上下文中的类型
|
||
if (_defaultDriverBaseDict.TryGetValue(pluginName, out var type))
|
||
{
|
||
var driver = (DriverBase)Activator.CreateInstance(type);
|
||
|
||
return driver;
|
||
}
|
||
|
||
// 构建插件目录路径
|
||
var dir = AppContext.BaseDirectory.CombinePathWithOs(DirName, filtResult.FileName);
|
||
|
||
// 先判断是否已经拥有插件模块
|
||
if (_driverBaseDict.TryGetValue(pluginName, out var value))
|
||
{
|
||
var driver = (DriverBase)Activator.CreateInstance(value);
|
||
return driver;
|
||
}
|
||
|
||
Assembly assembly = null;
|
||
// 根据路径获取DLL文件
|
||
var path = dir.CombinePathWithOs($"{filtResult.FileName}.dll");
|
||
assembly = GetAssembly(path, filtResult.FileName);
|
||
|
||
if (assembly != null)
|
||
{
|
||
// 根据采集/业务类型获取实际插件类
|
||
var driverType = assembly.GetTypes()
|
||
.Where(x => (typeof(CollectBase).IsAssignableFrom(x) || typeof(BusinessBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract)
|
||
.FirstOrDefault(it => it.Name == filtResult.TypeName);
|
||
|
||
if (driverType != null)
|
||
{
|
||
var driver = (DriverBase)Activator.CreateInstance(driverType);
|
||
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, pluginName));
|
||
_driverBaseDict.TryAdd(pluginName, driverType);
|
||
return driver;
|
||
}
|
||
// 抛出异常,插件类型不存在
|
||
throw new(string.Format(AppResource.LoadTypeFail1, pluginName));
|
||
}
|
||
else
|
||
{
|
||
// 抛出异常,插件文件不存在
|
||
throw new(string.Format(AppResource.LoadTypeFail2, path));
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
// 释放锁
|
||
_locker.Release();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定插件的特殊方法。
|
||
/// </summary>
|
||
/// <param name="pluginName">插件名称。</param>
|
||
/// <param name="driver">可选参数,插件的驱动基类对象,如果未提供,则会尝试从缓存中获取。</param>
|
||
/// <returns>返回列表</returns>
|
||
public List<DriverMethodInfo> GetDriverMethodInfos(string pluginName, IDriver? driver = null)
|
||
{
|
||
|
||
{
|
||
string cacheKey = $"{nameof(PluginService)}_{nameof(GetDriverMethodInfos)}_{System.Globalization.CultureInfo.CurrentUICulture.Name}";
|
||
// 如果未提供驱动基类对象,则尝试根据插件名称获取驱动对象
|
||
driver ??= GetDriver(pluginName); // 如果未提供驱动对象,则根据插件名称获取驱动对象
|
||
|
||
// 检查插件名称是否为空或null
|
||
if (!pluginName.IsNullOrEmpty())
|
||
{
|
||
// 尝试从缓存中获取指定插件的属性信息
|
||
var data = App.CacheService.HashGetAll<List<DriverMethodInfo>>(cacheKey);
|
||
// 如果缓存中存在指定插件的属性信息,则直接返回
|
||
if (data?.ContainsKey(pluginName) == true)
|
||
{
|
||
return data[pluginName];
|
||
}
|
||
}
|
||
|
||
// 如果未从缓存中获取到指定插件的属性信息,则尝试从驱动基类对象中获取
|
||
return SetDriverMethodInfosCache(driver, pluginName, cacheKey); // 获取并设置属性信息缓存
|
||
|
||
// 用于设置驱动方法信息缓存的内部方法
|
||
List<DriverMethodInfo> SetDriverMethodInfosCache(IDriver driver, string pluginName, string cacheKey)
|
||
{
|
||
// 获取驱动对象的方法信息,并筛选出带有 DynamicMethodAttribute 特性的方法
|
||
var dependencyPropertyWithInfos = driver.GetType().GetMethods()?.SelectMany(it =>
|
||
new[] { new { memberInfo = it, attribute = it.GetCustomAttribute<DynamicMethodAttribute>() } })
|
||
.Where(x => x.attribute != null).ToList()
|
||
.SelectMany(it => new[]
|
||
{
|
||
new DriverMethodInfo(){
|
||
Name=it.memberInfo.Name,
|
||
Description=it.attribute.Description,
|
||
Remark=it.attribute.Remark,
|
||
MethodInfo=it.memberInfo,
|
||
}
|
||
});
|
||
|
||
// 将方法信息转换为字典形式,并添加到缓存中
|
||
var result = dependencyPropertyWithInfos.ToList();
|
||
App.CacheService.HashAdd(cacheKey, pluginName, result);
|
||
|
||
// 返回获取到的属性信息字典
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定插件的属性类型及其信息,将其缓存在内存中
|
||
/// </summary>
|
||
/// <param name="pluginName">插件名称</param>
|
||
/// <param name="driver">驱动基类实例,可选参数</param>
|
||
/// <returns>返回包含属性名称及其信息的字典</returns>
|
||
public (IEnumerable<IEditorItem> EditorItems, object Model, Type PropertyUIType) GetDriverPropertyTypes(string pluginName, IDriver? driver = null)
|
||
{
|
||
|
||
{
|
||
string cacheKey = $"{nameof(PluginService)}_{nameof(GetDriverPropertyTypes)}_{System.Globalization.CultureInfo.CurrentUICulture.Name}";
|
||
|
||
driver ??= GetDriver(pluginName); // 如果 driver 为 null, 获取驱动实例
|
||
// 检查插件名称是否为空或空字符串
|
||
if (!pluginName.IsNullOrEmpty())
|
||
{
|
||
// 从缓存中获取属性类型数据
|
||
var data = App.CacheService.HashGetAll<List<IEditorItem>>(cacheKey);
|
||
// 如果缓存中存在数据
|
||
if (data?.ContainsKey(pluginName) == true)
|
||
{
|
||
// 返回缓存中存储的属性类型数据
|
||
var editorItems = data[pluginName];
|
||
return (editorItems, driver.DriverProperties, driver.DriverPropertyUIType);
|
||
}
|
||
}
|
||
// 如果缓存中不存在该插件的数据,则重新获取并缓存
|
||
|
||
return (SetCache(driver, pluginName, cacheKey), driver.DriverProperties, driver.DriverPropertyUIType); // 调用 SetCache 方法进行缓存并返回结果
|
||
|
||
// 定义 SetCache 方法,用于设置缓存并返回
|
||
IEnumerable<IEditorItem> SetCache(IDriver driver, string pluginName, string cacheKey)
|
||
{
|
||
var editorItems = PluginServiceUtil.GetEditorItems(driver.DriverProperties?.GetType()).ToList();
|
||
// 将结果存入缓存中,键为插件名称
|
||
App.CacheService.HashAdd(cacheKey, pluginName, editorItems);
|
||
|
||
return editorItems;
|
||
}
|
||
}
|
||
}
|
||
#if !Management
|
||
/// <summary>
|
||
/// 获取插件信息的方法,可以根据插件类型筛选插件列表。
|
||
/// </summary>
|
||
/// <param name="pluginType">要筛选的插件类型,可选参数</param>
|
||
/// <returns>符合条件的插件列表</returns>
|
||
public Task<List<PluginInfo>> GetPluginsAsync(PluginTypeEnum? pluginType = null)
|
||
{
|
||
// 获取完整的插件列表
|
||
var pluginList = PrivateGetList();
|
||
|
||
if (pluginType == null)
|
||
{
|
||
// 如果未指定插件类型,则返回完整的插件列表
|
||
return Task.FromResult(pluginList.ToList());
|
||
}
|
||
|
||
// 筛选出指定类型的插件
|
||
var filteredPlugins = pluginList.Where(c => c.PluginType == pluginType).ToList();
|
||
|
||
return Task.FromResult(filteredPlugins);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取插件信息的方法,可以根据插件类型筛选插件列表。
|
||
/// </summary>
|
||
/// <param name="pluginType">要筛选的插件类型,可选参数</param>
|
||
/// <returns>符合条件的插件列表</returns>
|
||
public List<PluginInfo> GetPluginList(PluginTypeEnum? pluginType = null)
|
||
{
|
||
// 获取完整的插件列表
|
||
var pluginList = PrivateGetList();
|
||
|
||
if (pluginType == null)
|
||
{
|
||
// 如果未指定插件类型,则返回完整的插件列表
|
||
return pluginList.ToList();
|
||
}
|
||
|
||
// 筛选出指定类型的插件
|
||
var filteredPlugins = pluginList.Where(c => c.PluginType == pluginType).ToList();
|
||
|
||
return filteredPlugins;
|
||
}
|
||
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 获取变量的属性类型
|
||
/// </summary>
|
||
public (IEnumerable<IEditorItem> EditorItems, object Model, Type VariablePropertyUIType) GetVariablePropertyTypes(string pluginName, BusinessBase? businessBase = null)
|
||
{
|
||
|
||
{
|
||
string cacheKey = $"{nameof(PluginService)}_{nameof(GetVariablePropertyTypes)}_{System.Globalization.CultureInfo.CurrentUICulture.Name}";
|
||
businessBase ??= (BusinessBase)GetDriver(pluginName); // 如果 driver 为 null, 获取驱动实例
|
||
|
||
var data = App.CacheService.HashGetAll<List<IEditorItem>>(cacheKey);
|
||
if (data?.ContainsKey(pluginName) == true)
|
||
{
|
||
return (data[pluginName], businessBase.VariablePropertys, businessBase.DriverVariablePropertyUIType);
|
||
}
|
||
// 如果缓存中不存在该插件的数据,则重新获取并缓存
|
||
return (SetCache(pluginName, cacheKey), businessBase.VariablePropertys, businessBase.DriverVariablePropertyUIType);
|
||
|
||
// 定义 SetCache 方法,用于设置缓存并返回
|
||
IEnumerable<IEditorItem> SetCache(string pluginName, string cacheKey)
|
||
{
|
||
var editorItems = businessBase.PluginVariablePropertyEditorItems;
|
||
// 将结果存入缓存中,键为插件名称
|
||
App.CacheService.HashAdd(cacheKey, pluginName, editorItems);
|
||
// 如果 dispose 参数为 true,则释放 driver 对象
|
||
return editorItems;
|
||
}
|
||
}
|
||
}
|
||
|
||
#if !Management
|
||
/// <summary>
|
||
/// 分页显示插件
|
||
/// </summary>
|
||
public async Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginType = null)
|
||
{
|
||
//指定关键词搜索为插件FullName
|
||
var query = (await GetPluginsAsync(pluginType).ConfigureAwait(false)).WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.FullName.Contains(options.SearchText)).GetQueryData(options);
|
||
return query;
|
||
}
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 移除全部插件
|
||
/// </summary>
|
||
public async Task ReloadPluginAsync()
|
||
{
|
||
try
|
||
{
|
||
await _locker.WaitAsync().ConfigureAwait(false);
|
||
_driverBaseDict.Clear();
|
||
foreach (var item in _assemblyLoadContextDict)
|
||
{
|
||
item.Value.AssemblyLoadContext.Unload();
|
||
}
|
||
ClearCache();
|
||
_assemblyLoadContextDict.Clear();
|
||
}
|
||
finally
|
||
{
|
||
_locker.Release();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
private WaitLock SaveLock = new(nameof(PluginService));
|
||
/// <summary>
|
||
/// 异步保存驱动程序信息。
|
||
/// </summary>
|
||
/// <param name="pluginAddPathInput">要保存的插件信息。</param>
|
||
[OperDesc("SavePlugin", isRecordPar: false, localizerType: typeof(PluginAddInput))]
|
||
public async Task SavePluginByPathAsync(PluginAddPathInput pluginAddPathInput)
|
||
{
|
||
try
|
||
{
|
||
// 等待锁可用
|
||
await SaveLock.WaitAsync().ConfigureAwait(false);
|
||
var tempDir = DirName;
|
||
// 创建程序集加载上下文
|
||
var assemblyLoadContext = new AssemblyLoadContext(CommonUtils.GetSingleId().ToString(), true);
|
||
// 存储其他文件的内存流列表
|
||
List<(string Name, MemoryStream MemoryStream)> otherFilesStreams = new();
|
||
|
||
// 获取主程序集文件名
|
||
var mainFileName = Path.GetFileNameWithoutExtension(pluginAddPathInput.MainFilePath);
|
||
string fullDir = string.Empty;
|
||
bool isDefaultDriver = false;
|
||
//判定是否上下文程序集
|
||
var defaultDriver = _defaultDriverBaseDict.FirstOrDefault(a => Path.GetFileNameWithoutExtension(new FileInfo(a.Value.Assembly.Location).Name) == mainFileName);
|
||
if (defaultDriver.Value != null)
|
||
{
|
||
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(defaultDriver.Key);
|
||
fullDir = Path.GetDirectoryName(filtResult.FileName);
|
||
isDefaultDriver = true;
|
||
}
|
||
else
|
||
{
|
||
// 构建插件文件夹绝对路径
|
||
fullDir = AppContext.BaseDirectory.CombinePathWithOs(tempDir, mainFileName);
|
||
isDefaultDriver = false;
|
||
}
|
||
// 获取主程序集文件流
|
||
MemoryStream mainMemoryStream = new MemoryStream();
|
||
try
|
||
{
|
||
// 构建主程序集绝对路径
|
||
var fullPath = fullDir.CombinePathWithOs(Path.GetFileName(pluginAddPathInput.MainFilePath));
|
||
|
||
|
||
using (var stream = File.Open(pluginAddPathInput.MainFilePath, FileMode.Open, FileAccess.Read))
|
||
{
|
||
|
||
await stream.CopyToAsync(mainMemoryStream).ConfigureAwait(false);
|
||
}
|
||
mainMemoryStream.Seek(0, SeekOrigin.Begin);
|
||
|
||
#region
|
||
// 先加载到内存,如果成功添加后再装载到文件
|
||
// 加载主程序集
|
||
var assembly = assemblyLoadContext.LoadFromStream(mainMemoryStream);
|
||
foreach (var item in pluginAddPathInput.OtherFilePaths ?? new())
|
||
{
|
||
// 获取附属文件流
|
||
using (var otherStream = File.Open(pluginAddPathInput.MainFilePath, FileMode.Open, FileAccess.Read))
|
||
{
|
||
|
||
MemoryStream memoryStream = new MemoryStream();
|
||
await otherStream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||
otherFilesStreams.Add((Path.GetFileName(item), memoryStream));
|
||
try
|
||
{
|
||
// 尝试加载附属程序集
|
||
assemblyLoadContext.LoadFromStream(memoryStream);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 加载失败时记录警告信息
|
||
_logger?.LogWarning(ex, string.Format(AppResource.LoadOtherFileFail, item));
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
// 获取驱动类型信息
|
||
var driverTypes = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x) || typeof(BusinessBase).IsAssignableFrom(x))
|
||
&& x.IsClass && !x.IsAbstract);
|
||
|
||
// 检查是否存在驱动类型
|
||
if (!driverTypes.Any())
|
||
{
|
||
throw new(AppResource.PluginNotFound);
|
||
}
|
||
|
||
assembly = null;
|
||
|
||
if (isDefaultDriver)
|
||
{
|
||
// 将主程序集保存到文件
|
||
await MarkSave(fullPath, mainMemoryStream).ConfigureAwait(false);
|
||
// 将其他文件保存到文件
|
||
foreach (var item in otherFilesStreams)
|
||
{
|
||
await MarkSave(fullDir.CombinePathWithOs(item.Name), item.MemoryStream).ConfigureAwait(false);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 将主程序集保存到文件
|
||
mainMemoryStream.Seek(0, SeekOrigin.Begin);
|
||
Directory.CreateDirectory(fullDir);// 创建插件文件夹
|
||
using FileStream fs = new(fullPath, FileMode.Create);
|
||
await mainMemoryStream.CopyToAsync(fs).ConfigureAwait(false);
|
||
// 将其他文件保存到文件
|
||
foreach (var item in otherFilesStreams)
|
||
{
|
||
item.MemoryStream.Seek(0, SeekOrigin.Begin);
|
||
using FileStream fs1 = new(fullDir.CombinePathWithOs(item.Name), FileMode.Create);
|
||
await item.MemoryStream.CopyToAsync(fs1).ConfigureAwait(false);
|
||
await item.MemoryStream.DisposeAsync().ConfigureAwait(false);
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (mainMemoryStream != null)
|
||
await mainMemoryStream.SafeDisposeAsync().ConfigureAwait(false);
|
||
otherFilesStreams.ForEach(async a =>
|
||
{
|
||
if (a.MemoryStream != null)
|
||
{
|
||
await a.MemoryStream.SafeDisposeAsync().ConfigureAwait(false);
|
||
}
|
||
});
|
||
|
||
// 卸载程序集加载上下文并清除缓存
|
||
assemblyLoadContext.Unload();
|
||
|
||
ClearCache();
|
||
|
||
try
|
||
{
|
||
// 卸载相同文件的插件域
|
||
DeletePlugin(mainFileName);
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (File.Exists(pluginAddPathInput.MainFilePath))
|
||
{
|
||
File.Delete(pluginAddPathInput.MainFilePath);
|
||
}
|
||
foreach (var item in pluginAddPathInput.OtherFilePaths)
|
||
{
|
||
if (File.Exists(item))
|
||
{
|
||
File.Delete(item);
|
||
}
|
||
}
|
||
// 释放锁资源
|
||
SaveLock.Release();
|
||
}
|
||
}
|
||
|
||
private static async Task MarkSave(string fullPath, MemoryStream stream)
|
||
{
|
||
var has = MarkDeletePlugin(fullPath);
|
||
var saveEx = string.Empty;
|
||
stream.Seek(0, SeekOrigin.Begin);
|
||
using FileStream fs = new($"{fullPath}{saveEx}", FileMode.Create);
|
||
await stream.CopyToAsync(fs).ConfigureAwait(false);
|
||
await stream.DisposeAsync().ConfigureAwait(false);
|
||
}
|
||
/// <summary>
|
||
/// 标记删除插件
|
||
/// </summary>
|
||
/// <param name="path">主程序集文件名称</param>
|
||
private static bool MarkDeletePlugin(string path)
|
||
{
|
||
var fileInfo = new FileInfo(path);
|
||
if (fileInfo.Exists)
|
||
fileInfo.MoveTo($"{path}{CommonUtils.GetSingleId()}{DelEx}", true);
|
||
else
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>删除备份文件</summary>
|
||
/// <param name="dest">目标目录</param>
|
||
private static void DeleteBackup(String dest)
|
||
{
|
||
// 删除备份
|
||
var di = dest.AsDirectory();
|
||
var fs = di.GetAllFiles($"*{DelEx}", true);
|
||
foreach (var item in fs)
|
||
{
|
||
try
|
||
{
|
||
item.Delete();
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置插件的属性值。
|
||
/// </summary>
|
||
/// <param name="driver">插件实例。</param>
|
||
/// <param name="deviceProperties">插件属性,检索相同名称的属性后写入。</param>
|
||
public void SetDriverProperties(IDriver driver, Dictionary<string, string> deviceProperties)
|
||
{
|
||
// 获取插件的属性信息列表
|
||
var pluginProperties = driver.DriverProperties?.GetType().GetRuntimeProperties()
|
||
// 筛选出带有 DynamicPropertyAttribute 特性的属性
|
||
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>(false) != null);
|
||
|
||
// 遍历插件的属性信息列表
|
||
foreach (var propertyInfo in pluginProperties ?? new List<PropertyInfo>())
|
||
{
|
||
// 在设备属性列表中查找与当前属性相同名称的属性
|
||
if (!deviceProperties.TryGetValue(propertyInfo.Name, out var deviceProperty))
|
||
{
|
||
continue;
|
||
}
|
||
// 获取设备属性的值,如果设备属性值为空,则将其转换为当前属性类型的默认值
|
||
var value = ThingsGatewayStringConverter.Default.Deserialize(null, deviceProperty, propertyInfo.PropertyType);
|
||
// 设置插件属性的值
|
||
propertyInfo.SetValue(driver.DriverProperties, value);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除缓存
|
||
/// </summary>
|
||
private void ClearCache()
|
||
{
|
||
|
||
{
|
||
App.CacheService.Remove(CacheKeyGetPluginOutputs);
|
||
App.CacheService.DelByPattern($"{nameof(PluginService)}_");
|
||
|
||
//多语言缓存清理
|
||
try
|
||
{
|
||
// 获取私有字段
|
||
FieldInfo fieldInfo = typeof(ResourceManagerStringLocalizerFactory).GetField("_localizerCache", BindingFlags.Instance | BindingFlags.NonPublic);
|
||
// 获取字段的值
|
||
var dictionary = (ConcurrentDictionary<string, ResourceManagerStringLocalizer>)fieldInfo.GetValue(App.StringLocalizerFactory);
|
||
foreach (var item in _assemblyLoadContextDict)
|
||
{
|
||
var ids = item.Value.Assembly.ExportedTypes.Select(b => b.AssemblyQualifiedName).ToHashSet();
|
||
// 移除特定键
|
||
dictionary.RemoveWhere(a => ids.Contains(a.Key));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
NewLife.Log.XTrace.WriteException(ex);
|
||
}
|
||
_ = Task.Run(() => _dispatchService.Dispatch(null));
|
||
}
|
||
}
|
||
|
||
#endregion public
|
||
|
||
/// <summary>
|
||
/// 删除插件域,卸载插件
|
||
/// </summary>
|
||
/// <param name="path">主程序集文件名称</param>
|
||
private void DeletePlugin(string path)
|
||
{
|
||
if (_assemblyLoadContextDict.TryGetValue(path, out var assemblyLoadContext))
|
||
{
|
||
//移除字典
|
||
_driverBaseDict.RemoveWhere(a => path == PluginInfoUtil.GetFileNameAndTypeName(a.Key).FileName);
|
||
_assemblyLoadContextDict.Remove(path);
|
||
//卸载
|
||
assemblyLoadContext.AssemblyLoadContext.Unload();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取程序集
|
||
/// </summary>
|
||
/// <param name="path">插件主文件绝对路径</param>
|
||
/// <param name="fileName">插件主文件名称</param>
|
||
/// <returns></returns>
|
||
private Assembly GetAssembly(string path, string fileName)
|
||
{
|
||
try
|
||
{
|
||
Assembly assembly = null;
|
||
//全部程序集路径
|
||
List<string> paths = new();
|
||
Directory.GetFiles(Path.GetDirectoryName(path), "*.dll").ToList().ForEach(a => paths.Add(a));
|
||
|
||
if (_assemblyLoadContextDict.TryGetValue(fileName, out (AssemblyLoadContext AssemblyLoadContext, Assembly Assembly) value))
|
||
{
|
||
assembly = value.Assembly;
|
||
}
|
||
else
|
||
{
|
||
//新建插件域,并注明可卸载
|
||
var assemblyLoadContext = new AssemblyLoadContext(fileName, true);
|
||
//获取插件程序集
|
||
assembly = GetAssembly(path, paths, assemblyLoadContext);
|
||
if (assembly == null)
|
||
{
|
||
assemblyLoadContext.Unload();
|
||
return null;
|
||
}
|
||
//添加到全局对象
|
||
_assemblyLoadContextDict.TryAdd(fileName, (assemblyLoadContext, assembly));
|
||
_logger?.LogInformation(string.Format(AppResource.AddPluginFile, path));
|
||
}
|
||
return assembly;
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取程序集
|
||
/// </summary>
|
||
/// <param name="path">主程序的路径</param>
|
||
/// <param name="paths">全部文件的路径</param>
|
||
/// <param name="assemblyLoadContext">当前插件域</param>
|
||
/// <returns></returns>
|
||
private Assembly GetAssembly(string path, List<string> paths, AssemblyLoadContext assemblyLoadContext)
|
||
{
|
||
Assembly assembly = null;
|
||
//var cacheId = CommonUtils.GetSingleId();
|
||
//foreach (var item in paths)
|
||
//{
|
||
// var dir = Path.GetDirectoryName(item).CombinePathWithOs($"Cache{cacheId}");
|
||
// var cachePath = dir.CombinePath(Path.GetFileName(item));
|
||
// Directory.CreateDirectory(dir);
|
||
// File.Copy(item, cachePath, true);
|
||
//}
|
||
foreach (var item in paths)
|
||
{
|
||
using var fs = new FileStream(item, FileMode.Open);
|
||
//var cachePath = Path.GetDirectoryName(item).CombinePathWithOs($"Cache{cacheId}").CombinePath(Path.GetFileName(item));
|
||
if (Path.GetFullPath(item) == Path.GetFullPath(path))
|
||
{
|
||
assembly = assemblyLoadContext.LoadFromStream(fs); //加载主程序集,并获取
|
||
|
||
//修改为从文件创立,满足roslyn引擎的要求
|
||
//assembly = assemblyLoadContext.LoadFromAssemblyPath(cachePath);
|
||
|
||
}
|
||
else
|
||
{
|
||
try
|
||
{
|
||
//assemblyLoadContext.LoadFromAssemblyPath(cachePath);
|
||
assemblyLoadContext.LoadFromStream(fs); //加载主程序集,并获取
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger?.LogWarning(ex, string.Format(AppResource.LoadOtherFileFail, item));
|
||
}
|
||
}
|
||
}
|
||
return assembly;
|
||
}
|
||
#if !Management
|
||
/// <summary>
|
||
/// 获取全部插件信息
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private IEnumerable<PluginInfo> PrivateGetList()
|
||
{
|
||
try
|
||
{
|
||
_locker.Wait();
|
||
|
||
// 从缓存中获取插件列表数据
|
||
var data = App.CacheService.Get<List<PluginInfo>>(CacheKeyGetPluginOutputs);
|
||
|
||
// 如果缓存中没有数据,则调用 GetPluginOutputs 方法获取数据,并将其存入缓存
|
||
if (data == null)
|
||
{
|
||
var pluginInfos = GetPluginOutputs();
|
||
App.CacheService.Set(CacheKeyGetPluginOutputs, pluginInfos.ToList());
|
||
return pluginInfos;
|
||
}
|
||
|
||
// 如果缓存中有数据,则直接返回
|
||
return data;
|
||
}
|
||
finally
|
||
{
|
||
// 释放锁资源
|
||
_locker.Release();
|
||
}
|
||
|
||
// 获取插件列表数据的私有方法
|
||
IEnumerable<PluginInfo> GetPluginOutputs()
|
||
{
|
||
var plugins = new List<PluginInfo>();
|
||
// 主程序上下文
|
||
string tempDir = DirName;
|
||
|
||
// 遍历程序集上下文默认驱动字典,生成默认驱动插件信息
|
||
foreach (var item in _defaultDriverBaseDict)
|
||
{
|
||
if (PluginServiceUtil.IsSupported(item.Value))
|
||
{
|
||
FileInfo fileInfo = new FileInfo(item.Value.Assembly.Location); //文件信息
|
||
DateTime lastWriteTime = fileInfo.LastWriteTime;//作为编译时间
|
||
|
||
var pluginInfo = new PluginInfo()
|
||
{
|
||
Name = item.Value.Name,//插件名称
|
||
FileName = Path.GetFileNameWithoutExtension(fileInfo.Name),//插件文件名称(分类)
|
||
PluginType = (typeof(CollectBase).IsAssignableFrom(item.Value)) ? PluginTypeEnum.Collect : PluginTypeEnum.Business, //插件类型
|
||
EducationPlugin = PluginServiceUtil.IsEducation(item.Value),
|
||
Version = item.Value.Assembly.GetName().Version.ToString(), //插件版本
|
||
|
||
LastWriteTime = lastWriteTime, //编译时间
|
||
};
|
||
if (!item.Value.Assembly.Location.IsNullOrEmpty())
|
||
pluginInfo.Directory = Path.GetDirectoryName(item.Value.Assembly.Location);
|
||
|
||
plugins.Add(pluginInfo);
|
||
}
|
||
}
|
||
|
||
// 获取插件文件夹路径列表
|
||
string[] folderPaths = Directory.GetDirectories(AppContext.BaseDirectory.CombinePathWithOs(tempDir));
|
||
|
||
// 遍历插件文件夹
|
||
foreach (string folderPath in folderPaths)
|
||
{
|
||
//当前插件文件夹
|
||
|
||
try
|
||
{
|
||
var driverMainName = Path.GetFileName(folderPath);
|
||
FileInfo fileInfo = new FileInfo(folderPath.CombinePathWithOs($"{driverMainName}.dll")); //插件主程序集名称约定为文件夹名称.dll
|
||
DateTime lastWriteTime = fileInfo.LastWriteTime;
|
||
|
||
// 加载插件程序集并获取其中的驱动类型信息
|
||
var assembly = GetAssembly(folderPath.CombinePathWithOs($"{driverMainName}.dll"), driverMainName);
|
||
|
||
if (assembly == null) continue;
|
||
var driverTypes = assembly?.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x) || typeof(BusinessBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract);
|
||
|
||
// 遍历驱动类型,生成插件信息,并将其添加到插件列表中
|
||
foreach (var type in driverTypes)
|
||
{
|
||
if (PluginServiceUtil.IsSupported(type))
|
||
{
|
||
// 先判断是否已经拥有插件模块
|
||
if (!_driverBaseDict.ContainsKey($"{driverMainName}.{type.Name}"))
|
||
{
|
||
//添加到字典
|
||
_driverBaseDict.TryAdd($"{driverMainName}.{type.Name}", type);
|
||
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, PluginInfoUtil.GetFullName(driverMainName, type.Name)));
|
||
}
|
||
var plugin = new PluginInfo()
|
||
{
|
||
Name = type.Name, //类型名称
|
||
FileName = $"{driverMainName}", //主程序集名称
|
||
PluginType = (typeof(CollectBase).IsAssignableFrom(type)) ? PluginTypeEnum.Collect : PluginTypeEnum.Business,//插件类型
|
||
Version = assembly.GetName().Version.ToString(),//插件版本
|
||
LastWriteTime = lastWriteTime, //编译时间
|
||
EducationPlugin = PluginServiceUtil.IsEducation(type),
|
||
};
|
||
plugin.Directory = folderPath;
|
||
|
||
plugins.Add(plugin);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录加载插件失败的日志
|
||
_logger?.LogWarning(ex, string.Format(AppResource.LoadPluginFail, Path.GetRelativePath(AppContext.BaseDirectory.CombinePathWithOs(tempDir), folderPath)));
|
||
}
|
||
}
|
||
return plugins.DistinctBy(a => a.FullName).OrderBy(a => a.EducationPlugin);
|
||
}
|
||
}
|
||
#endif
|
||
//public Task SavePlugin(PluginAddInput plugin)
|
||
//{
|
||
// return PluginInfoUtil.SavePlugin(plugin);
|
||
//}
|
||
}
|