mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-24 12:20:28 +08:00
393 lines
16 KiB
C#
393 lines
16 KiB
C#
#region copyright
|
||
//------------------------------------------------------------------------------
|
||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||
// QQ群:605534569
|
||
//------------------------------------------------------------------------------
|
||
#endregion
|
||
|
||
using Furion;
|
||
using Furion.DependencyInjection;
|
||
using Furion.FriendlyException;
|
||
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
using System.Collections.Concurrent;
|
||
using System.Data;
|
||
using System.Reflection;
|
||
using System.Runtime.Loader;
|
||
|
||
using ThingsGateway.Foundation.Extension.String;
|
||
|
||
using Yitter.IdGenerator;
|
||
|
||
namespace ThingsGateway.Gateway.Application;
|
||
/// <summary>
|
||
/// 驱动插件服务
|
||
/// </summary>
|
||
public class PluginSingletonService : ISingleton
|
||
{
|
||
private readonly ILogger<PluginSingletonService> _logger;
|
||
|
||
/// <inheritdoc cref="PluginSingletonService"/>
|
||
public PluginSingletonService(ILogger<PluginSingletonService> logger)
|
||
{
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 插件文件路径/插件程序集
|
||
/// </summary>
|
||
public ConcurrentDictionary<string, Assembly> AssemblyDict { get; private set; } = new();
|
||
|
||
/// <summary>
|
||
/// 插件文件路径/插件域
|
||
/// </summary>
|
||
public ConcurrentDictionary<string, AssemblyLoadContext> AssemblyLoadContextDict { get; private set; } = new();
|
||
|
||
/// <summary>
|
||
/// 插件ID/插件Type
|
||
/// </summary>
|
||
public ConcurrentDictionary<long, Type> DriverPluginDict { get; private set; } = new();
|
||
|
||
/// <summary>
|
||
/// 获取插件
|
||
/// </summary>
|
||
/// <param name="plugin"></param>
|
||
/// <returns></returns>
|
||
public DriverBase GetDriver(DriverPlugin plugin)
|
||
{
|
||
lock (this)
|
||
{
|
||
//添加默认上下文中继承类获取
|
||
switch (plugin.DriverTypeEnum)
|
||
{
|
||
case DriverEnum.Collect:
|
||
var driverType = App.EffectiveTypes.Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
|
||
if (driverType != null)
|
||
{
|
||
return GetDriver(plugin, driverType);
|
||
}
|
||
break;
|
||
case DriverEnum.Upload:
|
||
var upLoadType = App.EffectiveTypes.Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
|
||
if (upLoadType != null)
|
||
{
|
||
return GetDriver(plugin, upLoadType);
|
||
}
|
||
break;
|
||
}
|
||
|
||
|
||
//先判断是否已经拥有插件模块
|
||
if (DriverPluginDict.ContainsKey(plugin.Id))
|
||
{
|
||
var driver = (DriverBase)Activator.CreateInstance(DriverPluginDict[plugin.Id]);
|
||
driver.DriverPlugin = plugin;
|
||
return driver;
|
||
}
|
||
Assembly assembly = null;
|
||
|
||
_logger?.LogInformation($"添加插件文件:{plugin.FilePath}");
|
||
|
||
//根据路径获取dll文件
|
||
//主程序集路径
|
||
var path = AppContext.BaseDirectory.CombinePathOS(plugin.FilePath);
|
||
//全部程序集路径
|
||
List<string> paths = new();
|
||
Directory.GetFiles(Path.GetDirectoryName(path), "*.dll").ToList().
|
||
ForEach(a => paths.Add(a.Replace("\\", "/")));
|
||
|
||
if (AssemblyDict.ContainsKey(plugin.FilePath))
|
||
{
|
||
assembly = AssemblyDict[plugin.FilePath];
|
||
}
|
||
else
|
||
{
|
||
//新建插件域,并注明可卸载
|
||
var assemblyLoadContext = new AssemblyLoadContext(plugin.Id.ToString(), true);
|
||
//获取插件程序集
|
||
assembly = GetAssembly(path, paths, assemblyLoadContext);
|
||
//添加到全局对象
|
||
AssemblyLoadContextDict.TryAdd(plugin.FilePath, assemblyLoadContext);
|
||
AssemblyDict.TryAdd(plugin.FilePath, assembly);
|
||
}
|
||
|
||
if (assembly != null)
|
||
{
|
||
//根据采集/上传类型获取实际插件类
|
||
switch (plugin.DriverTypeEnum)
|
||
{
|
||
case DriverEnum.Collect:
|
||
var driverType = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
|
||
if (driverType != null)
|
||
{
|
||
return GetDriver(plugin, driverType);
|
||
}
|
||
break;
|
||
case DriverEnum.Upload:
|
||
var upLoadType = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName);
|
||
if (upLoadType != null)
|
||
{
|
||
return GetDriver(plugin, upLoadType);
|
||
}
|
||
break;
|
||
}
|
||
|
||
throw new Exception($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 失败,{plugin.AssembleName}不存在");
|
||
|
||
}
|
||
else
|
||
{
|
||
throw new Exception($"加载驱动插件 {path} 失败,文件不存在");
|
||
}
|
||
DriverBase GetDriver(DriverPlugin plugin, Type driverType)
|
||
{
|
||
var driver = (DriverBase)Activator.CreateInstance(driverType);
|
||
_logger?.LogInformation($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 成功");
|
||
DriverPluginDict.TryAdd(plugin.Id, driverType);
|
||
driver.DriverPlugin = plugin;
|
||
return driver;
|
||
}
|
||
Assembly GetAssembly(string path, List<string> paths, AssemblyLoadContext assemblyLoadContext)
|
||
{
|
||
Assembly assembly = null;
|
||
foreach (var item in paths)
|
||
{
|
||
using var fs = new FileStream(item, FileMode.Open);
|
||
if (item == path)
|
||
assembly = assemblyLoadContext.LoadFromStream(fs);
|
||
else
|
||
{
|
||
try
|
||
{
|
||
assemblyLoadContext.LoadFromStream(fs);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogWarning($"尝试加载附属程序集{item}失败,如果此程序集为非引用,比如非托管DllImport,可以忽略此警告。错误信息:{(ex.Message)}");
|
||
}
|
||
}
|
||
}
|
||
return assembly;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取插件的属性值
|
||
/// </summary>
|
||
public List<DependencyProperty> GetDriverProperties(DriverBase driver)
|
||
{
|
||
var data = driver.DriverPropertys?.GetType().GetPropertiesWithCache().SelectMany(it =>
|
||
new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute<DevicePropertyAttribute>() } })
|
||
.Where(x => x.devicePropertyAttribute != null).ToList()
|
||
.SelectMany(it => new[]
|
||
{
|
||
new DependencyProperty(){
|
||
PropertyName=it.property.Name,
|
||
Description=it.devicePropertyAttribute.Description,
|
||
Remark=it.devicePropertyAttribute.Remark,
|
||
Value=it.property.GetValue(driver.DriverPropertys)?.ToString(),
|
||
}
|
||
});
|
||
return data.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取插件的变量上传属性值
|
||
/// </summary>
|
||
public List<DependencyProperty> GetDriverVariableProperties(UpLoadBase driver)
|
||
{
|
||
var data = driver.VariablePropertys?.GetType().GetPropertiesWithCache()?.SelectMany(it =>
|
||
new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute<VariablePropertyAttribute>() } })
|
||
?.Where(x => x.devicePropertyAttribute != null).ToList()
|
||
?.SelectMany(it => new[]
|
||
{
|
||
new DependencyProperty(){
|
||
PropertyName=it.property.Name,
|
||
Description=it.devicePropertyAttribute.Description,
|
||
Remark=it.devicePropertyAttribute.Remark,
|
||
Value=it.property.GetValue(driver.VariablePropertys)?.ToString(),
|
||
}
|
||
});
|
||
return data?.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取插件方法
|
||
/// </summary>
|
||
/// <param name="driver"></param>
|
||
/// <returns></returns>
|
||
public List<MethodInfo> GetMethod(DriverBase driver)
|
||
{
|
||
return driver.GetType().GetMethods().Where(
|
||
x => x.GetCustomAttribute(typeof(DeviceMethodAttribute)) != null).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置插件的属性值
|
||
/// </summary>
|
||
public void SetDriverProperties(DriverBase driver, List<DependencyProperty> deviceProperties)
|
||
{
|
||
var pluginPropertys = driver.DriverPropertys?.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute(typeof(DevicePropertyAttribute)) != null)?.ToList();
|
||
foreach (var propertyInfo in pluginPropertys ?? new())
|
||
{
|
||
var deviceProperty = deviceProperties.FirstOrDefault(x => x.PropertyName == propertyInfo.Name);
|
||
if (deviceProperty == null) continue;
|
||
var value = propertyInfo.PropertyType.ObjToTypeValue(deviceProperty?.Value ?? "");
|
||
propertyInfo.SetValue(driver.DriverPropertys, value);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试添加插件,返回插件表示类,方法完成后会完全卸载插件
|
||
/// </summary>
|
||
/// <param name="plugin"></param>
|
||
/// <returns></returns>
|
||
public async Task<List<DriverPlugin>> TryAddDriverAsync(DriverPluginAddInput plugin)
|
||
{
|
||
var assemblyLoadContext = new AssemblyLoadContext(YitIdHelper.NextId().ToString(), true);
|
||
try
|
||
{
|
||
var driverPlugins = new List<DriverPlugin>();
|
||
var maxFileSize = 100 * 1024 * 1024;//最大100m
|
||
//主程序集名称
|
||
var mainFileName = Path.GetFileNameWithoutExtension(plugin.MainFile.Name);
|
||
//插件文件夹相对路径
|
||
var dir = "Plugins".CombinePathOS(mainFileName);
|
||
//插件文件夹绝对路径
|
||
var fullDir = AppContext.BaseDirectory.CombinePathOS(dir);
|
||
//主程序集相对路径
|
||
var path = dir.CombinePathOS(plugin.MainFile.Name);
|
||
//主程序集绝对路径
|
||
var fullPath = fullDir.CombinePathOS(plugin.MainFile.Name);
|
||
|
||
//主程序集相对路径
|
||
//获取文件流
|
||
using var stream = plugin.MainFile.OpenReadStream(maxFileSize);
|
||
Directory.CreateDirectory(fullDir);//创建插件文件夹
|
||
using FileStream fs = new(fullPath, FileMode.Create);
|
||
await stream.CopyToAsync(fs);
|
||
fs.Seek(0, SeekOrigin.Begin);
|
||
|
||
//获取主程序集
|
||
var assembly = assemblyLoadContext.LoadFromStream(fs);
|
||
foreach (var item in plugin.OtherFiles)
|
||
{
|
||
using var otherStream = item.OpenReadStream(maxFileSize);
|
||
using FileStream fs1 = new(fullDir.CombinePathOS(item.Name), FileMode.Create);
|
||
await otherStream.CopyToAsync(fs1);
|
||
fs1.Seek(0, SeekOrigin.Begin);
|
||
try
|
||
{
|
||
assemblyLoadContext.LoadFromStream(fs1);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogWarning($"尝试加载附属程序集{item}失败,如果此程序集为非引用,比如非托管DllImport,可以忽略此警告。错误信息:{(ex.Message)}");
|
||
}
|
||
}
|
||
if (assembly != null)
|
||
{
|
||
//获取插件的相关信息
|
||
var collectBase = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList();
|
||
for (int i = 0; i < collectBase.Count; i++)
|
||
{
|
||
var item = collectBase[i];
|
||
driverPlugins.Add(new DriverPlugin()
|
||
{
|
||
AssembleName = item.Name,
|
||
DriverTypeEnum = DriverEnum.Collect,
|
||
FilePath = path,
|
||
FileName = mainFileName,
|
||
});
|
||
}
|
||
collectBase.ForEach(a => a = null);
|
||
collectBase.Clear();
|
||
collectBase = null;
|
||
var upLoadBase = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList();
|
||
for (int i = 0; i < upLoadBase.Count; i++)
|
||
{
|
||
var item = upLoadBase[i];
|
||
driverPlugins.Add(new DriverPlugin()
|
||
{
|
||
AssembleName = item.Name,
|
||
DriverTypeEnum = DriverEnum.Upload,
|
||
FilePath = path,
|
||
FileName = mainFileName,
|
||
});
|
||
}
|
||
upLoadBase.ForEach(a => a = null);
|
||
upLoadBase.Clear();
|
||
upLoadBase = null;
|
||
}
|
||
else
|
||
{
|
||
throw Oops.Bah("加载驱动文件失败");
|
||
}
|
||
if (driverPlugins.Count == 0)
|
||
{
|
||
throw Oops.Bah("找不到对应的驱动");
|
||
}
|
||
assembly = null;
|
||
|
||
var driverPluginCaches = App.GetService<DriverPluginService>().GetCacheList();
|
||
var filePaths = driverPlugins.Select(a => a.FilePath).Distinct().ToList();
|
||
driverPluginCaches = driverPluginCaches.Where(a => filePaths.Contains(a.FilePath)).ToList();
|
||
foreach (var item in driverPluginCaches)
|
||
{
|
||
//卸载相同文件的插件域,这么可能会重复执行,但只会卸载一次
|
||
DeleteDriver(item.FilePath, driverPluginCaches.Select(a => a.Id).ToList());
|
||
}
|
||
return driverPlugins;
|
||
}
|
||
finally
|
||
{
|
||
assemblyLoadContext.Unload();
|
||
}
|
||
}
|
||
|
||
private List<WeakReference> WeakReferences = new();
|
||
/// <summary>
|
||
/// 删除插件
|
||
/// </summary>
|
||
private void DeleteDriver(string path, List<long> ids)
|
||
{
|
||
if (AssemblyLoadContextDict.TryGetValue(path, out var assemblyLoadContext))
|
||
{
|
||
ids ??= new();
|
||
DriverPluginDict.RemoveWhere(a => ids.Contains(a.Key));
|
||
AssemblyDict.Remove(path);
|
||
AssemblyLoadContextDict.Remove(path);
|
||
WeakReference alcWeakRef = new WeakReference(assemblyLoadContext, true);
|
||
WeakReferences.Add(alcWeakRef);
|
||
assemblyLoadContext.Unload();
|
||
GC.Collect();
|
||
}
|
||
try
|
||
{
|
||
foreach (WeakReference item in WeakReferences)
|
||
{
|
||
if (item.IsAlive)
|
||
{
|
||
GC.Collect();
|
||
}
|
||
else
|
||
{
|
||
WeakReferences.Remove(item);
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
|
||
}
|
||
|
||
}
|
||
|
||
} |