Files
ThingsGateway/framework/Web/ThingsGateway.Gateway.Application/Services/PluginInfo/PluginSingletonService.cs
2023-10-22 02:26:18 +08:00

393 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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
{
}
}
}