!69 更新依赖

This commit is contained in:
Diego
2025-08-06 11:25:31 +00:00
parent 34000d8d7d
commit ebd71e807b
197 changed files with 1460 additions and 4729 deletions

View File

@@ -27,16 +27,17 @@
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertDatas">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public int BulkCopy<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public int BulkCopy<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
int result = 0;
// 使用分页方式处理大数据量插入
db.Utilities.PageEach(insertDatas, pageSize, pageItems =>
{
// 同步调用批量插入API并累加结果
result += questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).GetAwaiter().GetResult();
result += questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).GetAwaiter().GetResult();
});
return result;
}
@@ -46,16 +47,17 @@
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertDatas">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
int result = 0;
// 异步分页处理大数据量插入
await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems =>
{
// 异步调用批量插入API并累加结果
result += await questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).ConfigureAwait(false);
result += await questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).ConfigureAwait(false);
}).ConfigureAwait(false);
return result;
}

View File

@@ -7,16 +7,12 @@ namespace ThingsGateway.SqlSugar
/// <summary>
/// 绑定RestAPI需要的信息
/// </summary>
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string httpPort, ref string username, ref string password)
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string username, ref string password)
{
if (builder.TryGetValue("Host", out object hostValue))
{
host = Convert.ToString(hostValue);
}
if (builder.TryGetValue("HttpPort", out object httpPortValue))
{
httpPort = Convert.ToString(httpPortValue);
}
if (builder.TryGetValue("Username", out object usernameValue))
{
username = Convert.ToString(usernameValue);

View File

@@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar
/// 初始化 QuestDbRestAPI 实例
/// </summary>
/// <param name="db">SqlSugar 数据库客户端</param>
public QuestDbRestAPI(ISqlSugarClient db)
/// <param name="httpPort">restApi端口</param>
public QuestDbRestAPI(ISqlSugarClient db, int httpPort = 9000)
{
var builder = new DbConnectionStringBuilder();
builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
this.db = db;
string httpPort = String.Empty;
string host = String.Empty;
string username = String.Empty;
string password = String.Empty;
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref httpPort, ref username, ref password);
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref username, ref password);
BindHost(host, httpPort, username, password);
}
@@ -68,34 +68,34 @@ namespace ThingsGateway.SqlSugar
return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
}
/// <summary>
/// 异步批量插入单条数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertData">要插入的数据</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>影响的行数</returns>
public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
if (db.CurrentConnectionConfig.MoreSettings == null)
db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
var sql = db.InsertableT(insertData).ToSqlString();
var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
}
///// <summary>
///// 异步批量插入单条数据
///// </summary>
///// <typeparam name="T">数据类型</typeparam>
///// <param name="insertData">要插入的数据</param>
///// <param name="dateFormat">日期格式字符串</param>
///// <returns>影响的行数</returns>
//public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
//{
// if (db.CurrentConnectionConfig.MoreSettings == null)
// db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
// db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
// var sql = db.InsertableT(insertData).ToSqlString();
// var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
// return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
//}
/// <summary>
/// 同步批量插入单条数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertData">要插入的数据</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>影响的行数</returns>
public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
}
///// <summary>
///// 同步批量插入单条数据
///// </summary>
///// <typeparam name="T">数据类型</typeparam>
///// <param name="insertData">要插入的数据</param>
///// <param name="dateFormat">日期格式字符串</param>
///// <returns>影响的行数</returns>
//public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
//{
// return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
//}
/// <summary>
/// 创建分页批量插入器
@@ -115,9 +115,10 @@ namespace ThingsGateway.SqlSugar
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertList">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
var result = 0;
var fileName = $"{Guid.NewGuid()}.csv";
@@ -127,12 +128,12 @@ namespace ThingsGateway.SqlSugar
// 准备多部分表单数据
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
var list = new List<Hashtable>();
var name = db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
// 获取或创建列信息缓存
var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name));
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
// 构建schema信息
columns.ForEach(d =>
@@ -170,8 +171,8 @@ namespace ThingsGateway.SqlSugar
// 准备HTTP请求内容
using var httpContent = new MultipartFormDataContent(boundary);
using var fileStream = File.OpenRead(filePath);
if (!string.IsNullOrWhiteSpace(this.authorization))
client.DefaultRequestHeaders.Add("Authorization", this.authorization);
//if (!string.IsNullOrWhiteSpace(this.authorization))
// client.DefaultRequestHeaders.Add("Authorization", this.authorization);
httpContent.Add(new StringContent(schema), "schema");
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
@@ -184,7 +185,7 @@ namespace ThingsGateway.SqlSugar
// 发送请求并处理响应
var httpResponseMessage =
await Post(client, name, httpContent).ConfigureAwait(false);
await Post(client, tableName, httpContent).ConfigureAwait(false);
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
@@ -266,11 +267,12 @@ namespace ThingsGateway.SqlSugar
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertList">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public int BulkCopy<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public int BulkCopy<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult();
return BulkCopyAsync(insertList, tableName, dateFormat).GetAwaiter().GetResult();
}
/// <summary>
@@ -280,7 +282,7 @@ namespace ThingsGateway.SqlSugar
/// <param name="httpPort">HTTP端口</param>
/// <param name="username">用户名</param>
/// <param name="password">密码</param>
private void BindHost(string host, string httpPort, string username, string password)
private void BindHost(string host, int httpPort, string username, string password)
{
url = host;
if (url.EndsWith('/'))

View File

@@ -2,9 +2,9 @@
{
public static class QuestDbSqlSugarClientExtensions
{
public static QuestDbRestAPI RestApi(this ISqlSugarClient db)
public static QuestDbRestAPI RestApi(this ISqlSugarClient db, int httpPort = 9000)
{
return new QuestDbRestAPI(db);
return new QuestDbRestAPI(db, httpPort);
}
}
}

View File

@@ -77,7 +77,8 @@ public partial class LogConsole : IDisposable
[Inject]
private ToastService ToastService { get; set; }
[Inject]
ITextFileReadService TextFileReadService { get; set; }
public void Dispose()
{
Disposed = true;
@@ -94,7 +95,7 @@ public partial class LogConsole : IDisposable
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
var files = await TextFileReadService.GetLogFiles(LogPath);
if (!files.IsSuccess)
{
Messages = new List<LogMessage>();
@@ -105,7 +106,7 @@ public partial class LogConsole : IDisposable
await Task.Run(async () =>
{
Stopwatch sw = Stopwatch.StartNew();
var result = TextFileReader.LastLog(files.Content.FirstOrDefault());
var result = await TextFileReadService.LastLogData(files.Content.FirstOrDefault());
if (result.IsSuccess)
{
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
@@ -143,7 +144,7 @@ public partial class LogConsole : IDisposable
{
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
var files = await TextFileReadService.GetLogFiles(LogPath);
if (files.IsSuccess)
{
foreach (var item in files.Content)

View File

@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
public async Task OnLogExport(string logPath)
{
var files = TextFileReader.GetFiles(logPath);
var files = TextFileReader.GetLogFiles(logPath);
if (!files.IsSuccess)
{
return;

View File

@@ -33,5 +33,6 @@ public class Startup : AppStartup
}
services.AddScoped<IPlatformService, PlatformService>();
services.AddSingleton<ITextFileReadService, TextFileReadService>();
}
}

View File

@@ -59,7 +59,7 @@ public static class TextFileReader
/// </summary>
/// <param name="directoryPath">目录路径</param>
/// <returns>包含文件信息的列表</returns>
public static OperResult<List<string>> GetFiles(string directoryPath)
public static OperResult<List<string>> GetLogFiles(string directoryPath)
{
OperResult<List<string>> result = new(); // 初始化结果对象
// 检查目录是否存在
@@ -91,7 +91,7 @@ public static class TextFileReader
return result;
}
public static OperResult<List<LogData>> LastLog(string file, int lineCount = 200)
public static OperResult<List<LogData>> LastLogData(string file, int lineCount = 200)
{
if (!File.Exists(file))
return new OperResult<List<LogData>>("The file path is invalid");
@@ -104,7 +104,7 @@ public static class TextFileReader
{
var fileInfo = new FileInfo(file);
var length = fileInfo.Length;
var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLog)}_{file})";
var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLogData)}_{file})";
if (_cache.TryGetValue<LogDataCache>(cacheKey, out var cachedData))
{
if (cachedData != null && cachedData.Length == length)

View File

@@ -0,0 +1,24 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
public interface ITextFileReadService
{
/// <summary>
/// 获取指定目录下所有文件信息
/// </summary>
/// <param name="directoryPath">目录路径</param>
/// <returns>包含文件信息的列表</returns>
public Task<OperResult<List<string>>> GetLogFiles(string directoryPath);
public Task<OperResult<List<LogData>>> LastLogData(string file, int lineCount = 200);
}

View File

@@ -8,13 +8,13 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Riok.Mapperly.Abstractions;
using TouchSocket.Dmtp;
namespace ThingsGateway.Upgrade;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class UpgradeMapper
namespace ThingsGateway.Foundation;
public class TextFileReadService : ITextFileReadService
{
public static partial List<TcpSessionClientDto> AdaptListTcpSessionClientDto(this List<TcpDmtpSessionClient> src);
public Task<OperResult<List<string>>> GetLogFiles(string directoryPath) => Task.FromResult(TextFileReader.GetLogFiles(directoryPath));
public Task<OperResult<List<LogData>>> LastLogData(string file, int lineCount = 200) => Task.FromResult(TextFileReader.LastLogData(file, lineCount));
}

View File

@@ -1,43 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Management;
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
[Route("openApi/autoUpdate")]
[RolePermission]
[RequestAudit]
[ApiController]
[Authorize(AuthenticationSchemes = "Bearer")]
public class AutoUpdateController : ControllerBase
{
private IUpdateZipFileHostedService _updateZipFileService;
public AutoUpdateController(IUpdateZipFileHostedService updateZipFileService)
{
_updateZipFileService = updateZipFileService;
}
/// <summary>
/// 检查更新
/// </summary>
/// <returns></returns>
[HttpPost("update")]
public async Task Update()
{
var data = await _updateZipFileService.GetList().ConfigureAwait(false);
if (data.Count != 0)
await _updateZipFileService.Update(data.OrderByDescending(a => a.Version).FirstOrDefault()).ConfigureAwait(false);
}
}

View File

@@ -145,7 +145,7 @@ public class RuntimeInfoController : ControllerBase, IRpcServer
public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery][TouchSocket.WebApi.FromBody] PluginInfoPageInput input)
{
//指定关键词搜索为插件FullName
return GlobalData.PluginService.GetList().WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name)
return (GlobalData.PluginService.GetPluginListSync()).WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name)
.ToPagedList(input);
}
}

View File

@@ -8,7 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
namespace Microsoft.Extensions.DependencyInjection;
@@ -19,30 +18,19 @@ namespace Microsoft.Extensions.DependencyInjection;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddGatewayHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
{
services.AddSingleton<THostedService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>(seriveProvider => seriveProvider.GetService<THostedService>()));
return services;
}
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
public static IServiceCollection AddGatewayHostedService<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
where TService : class, IHostedService
where TService : class
where THostedService : class, IHostedService, TService
{
services.AddSingleton(typeof(TService), typeof(THostedService));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, TService>(seriveProvider => seriveProvider.GetService<TService>()));
services.AddSingleton<THostedService>();
services.AddHostedService<THostedService>(a => a.GetService<THostedService>());
services.AddSingleton<TService>(a => a.GetService<THostedService>());
return services;
}
}

View File

@@ -1,4 +1,22 @@
{
"ThingsGateway.Management.Application._Imports": {
"Restart": "Restart",
"Upgrade": "Upgrade"
},
"ThingsGateway.Management.Application.SaveUpdateZipFile": {
"DownTemplate": "Download Template",
"SaveUpdateZipFile": "Upload Version Package"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "AppName",
"Architecture": "Architecture",
"DotNetVersion": "DotNetVersion",
"FilePath": "FilePath",
"FileSize": "FileSize",
"MinimumCompatibleVersion": "MinimumCompatibleVersion",
"OSPlatform": "OSPlatform",
"Version": "Version"
},
"ThingsGateway.Gateway.Application.DefaultDiagram": {
@@ -53,11 +71,7 @@
},
"ThingsGateway.Management.AutoUpdateController": {
"AutoUpdateController": "AutoUpdate",
"Update": "Update"
},
"ThingsGateway.Management.RedundancyHostedService": {
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
"ErrorSynchronizingData": "Synchronize data to standby site error",
"RedundancyDisable": "Redundant gateway site not enabled",
"RedundancyDup": "Redundant station settings duplicated",
@@ -67,10 +81,10 @@
"SwitchNormalState": "Local machine (primary site) will switch to normal state",
"SwitchSlaveState": "Master site has recovered, local machine (standby) will switch to standby state"
},
"ThingsGateway.Management.RedundancyOptions": {
"ThingsGateway.Gateway.Application.RedundancyOptions": {
"Confirm": "Confirm switching to redundant state",
"Enable": "Enable Dual-Machine Redundancy",
"ForcedSync": "Forced Synchronous",
"RedundancyForcedSync": "Forced Synchronous",
"ForcedSyncWarning": "Forcing synchronization will generate database configuration information.Are you sure you want to continue?",
"HeartbeatInterval": "Heartbeat Interval",
"IsMaster": "IsMaster",
@@ -86,13 +100,13 @@
"SyncInterval": "Data Synchronization Interval",
"VerifyToken": "Verification Token"
},
"ThingsGateway.Management.RedundancyService": {
"ThingsGateway.Gateway.Application.RedundancyService": {
"EditRedundancyOption": "EditRedundancyOption"
},
"ThingsGateway.Management.UpdateZipFileHostedService": {
"ThingsGateway.Gateway.Application.UpdateZipFileService": {
"Update": "New version detected"
},
"ThingsGateway.Upgrade.UpdateZipFile": {
"ThingsGateway.Gateway.Application.UpdateZipFile": {
"AppName": "AppName",
"Architecture": "Architecture",
"DotNetVersion": "DotNetVersion",

View File

@@ -1,4 +1,22 @@
{
"ThingsGateway.Management.Application._Imports": {
"Restart": "重启",
"Upgrade": "更新"
},
"ThingsGateway.Management.Application.SaveUpdateZipFile": {
"DownTemplate": "下载模板",
"SaveUpdateZipFile": "上传版本包"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "名称",
"Architecture": "架构",
"DotNetVersion": ".net版本",
"FilePath": "文件路径",
"FileSize": "文件大小",
"MinimumCompatibleVersion": "最小兼容版本",
"OSPlatform": "系统版本",
"Version": "版本"
},
"ThingsGateway.Gateway.Application.DefaultDiagram": {
@@ -51,11 +69,8 @@
"RulesId": "名称"
},
"ThingsGateway.Management.AutoUpdateController": {
"AutoUpdateController": "程序更新",
"Update": "更新"
},
"ThingsGateway.Management.RedundancyHostedService": {
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
"ErrorSynchronizingData": "同步数据到从站错误",
"RedundancyDisable": "不启用网关冗余站点",
"RedundancyDup": "主备站设置重复",
@@ -65,10 +80,10 @@
"SwitchNormalState": "本机(主站)将切换到正常状态",
"SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态"
},
"ThingsGateway.Management.RedundancyOptions": {
"ThingsGateway.Gateway.Application.RedundancyOptions": {
"Confirm": "确认切换冗余状态",
"Enable": "启用双机冗余",
"ForcedSync": "强制同步",
"RedundancyForcedSync": "强制同步",
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?",
"HeartbeatInterval": "心跳间隔",
"IsMaster": "是否为主站",
@@ -84,13 +99,13 @@
"SyncInterval": "数据同步间隔",
"VerifyToken": "Token"
},
"ThingsGateway.Management.RedundancyService": {
"ThingsGateway.Gateway.Application.RedundancyService": {
"EditRedundancyOption": "修改网关冗余配置"
},
"ThingsGateway.Management.UpdateZipFileHostedService": {
"ThingsGateway.Gateway.Application.UpdateZipFileService": {
"Update": "检测到新版本"
},
"ThingsGateway.Upgrade.UpdateZipFile": {
"ThingsGateway.Gateway.Application.UpdateZipFile": {
"AppName": "名称",
"Architecture": "架构",
"DotNetVersion": ".net版本",

View File

@@ -10,8 +10,6 @@
using Riok.Mapperly.Abstractions;
using ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class GatewayMapper

View File

@@ -117,7 +117,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
public void Init()
{
// 通过插件名称获取插件信息
PluginInfo = GlobalData.PluginService.GetList().FirstOrDefault(A => A.FullName == PluginName);
PluginInfo = GlobalData.PluginService.GetPluginListSync().FirstOrDefault(A => A.FullName == PluginName);
GlobalData.IdChannels.TryRemove(Id, out _);
GlobalData.Channels.TryRemove(Name, out _);

View File

@@ -27,7 +27,7 @@ public class PluginInfo
/// 插件文件名称.插件类型名称
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public string FullName => PluginServiceUtil.GetFullName(FileName, Name);
public string FullName => PluginInfoUtil.GetFullName(FileName, Name);
/// <summary>
/// 插件文件名称
@@ -70,8 +70,5 @@ public class PluginInfo
/// </summary>
[IgnoreExcel]
[SugarColumn(IsIgnore = true)]
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public string Directory { get; set; }
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 系统配置种子数据

View File

@@ -8,11 +8,9 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
namespace ThingsGateway.Gateway.Application;
public interface IAlarmHostedService : IHostedService
public interface IAlarmHostedService
{
/// <summary>
/// 确认报警

View File

@@ -260,7 +260,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
HashSet<long>? channel = null;
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginListSync(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = GlobalData.IdChannels.Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);

View File

@@ -216,7 +216,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginListSync(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (GlobalData.IdChannels).Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
@@ -238,7 +238,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginListSync(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (GlobalData.IdChannels).Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
@@ -417,7 +417,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = _pluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
var driverPluginNameDict = _pluginService.GetPluginListSync().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
foreach (var sheetName in sheetNames)
{

View File

@@ -50,7 +50,7 @@ HashSet<string> pluginSheetNames,
foreach (var plugin in pluginSheetNames)
{
var filtered = FilterPluginDevices(data, plugin, channelDicts);
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
result.Add(filtResult.TypeName, pluginSheets);
}
@@ -75,7 +75,7 @@ string? channelName = null)
foreach (var plugin in pluginSheetNames)
{
var filtered = FilterPluginDevices(data2, plugin, channelDicts);
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
result.Add(filtResult.TypeName, pluginSheets);
}
@@ -288,10 +288,10 @@ string? channelName)
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序的完整名称作为键构建字典
var driverPluginFullNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.FullName);
var driverPluginFullNameDict = GlobalData.PluginService.GetPluginListSync().ToDictionary(a => a.FullName);
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = GlobalData.PluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
var driverPluginNameDict = GlobalData.PluginService.GetPluginListSync().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
var sheetNames = uSheetDatas.sheets.Keys.ToList();

View File

@@ -21,7 +21,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
/// <summary>
/// 最新十条
/// </summary>
public async Task<List<BackendLog>> GetNewLog()
public async Task<List<BackendLog>> GetNewBackendLog()
{
using var db = GetDB();
var data = await db.Queryable<BackendLog>().OrderByDescending(a => a.LogTime).Take(10).ToListAsync().ConfigureAwait(false);
@@ -32,7 +32,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
/// 表格查询
/// </summary>
/// <param name="option">查询条件</param>
public Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option)
public Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option)
{
return QueryAsync(option);
}
@@ -58,7 +58,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
/// </summary>
/// <param name="day">天</param>
/// <returns>统计信息</returns>
public async Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day)
public async Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day)
{
using var db = GetDB();
//取最近七天

View File

@@ -10,13 +10,9 @@
using BootstrapBlazor.Components;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
public interface IBackendLogService : IRpcServer
public interface IBackendLogService
{
/// <summary>
/// 删除 BackendLog 表中的所有记录
@@ -24,29 +20,25 @@ public interface IBackendLogService : IRpcServer
/// <remarks>
/// 调用此方法会删除 BackendLog 表中的所有记录。
/// </remarks>
[DmtpRpc]
Task DeleteBackendLogAsync();
/// <summary>
/// 获取最新的十条 BackendLog 记录
/// </summary>
/// <returns>最新的十条记录</returns>
[DmtpRpc]
Task<List<BackendLog>> GetNewLog();
Task<List<BackendLog>> GetNewBackendLog();
/// <summary>
/// 分页查询 BackendLog 数据
/// </summary>
/// <param name="option">查询选项</param>
/// <returns>查询到的数据</returns>
[DmtpRpc]
Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option);
Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option);
/// <summary>
/// 获取最近一段时间内每天的后端日志统计信息
/// </summary>
/// <param name="day">要统计的天数</param>
/// <returns>按天统计的后端日志信息列表</returns>
[DmtpRpc]
Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day);
}

View File

@@ -26,19 +26,19 @@ public interface IRpcLogService
/// 获取最新的十条 RpcLog 记录
/// </summary>
/// <returns>最新的十条记录</returns>
Task<List<RpcLog>> GetNewLog();
Task<List<RpcLog>> GetNewRpcLog();
/// <summary>
/// 分页查询 RpcLog 数据
/// </summary>
/// <param name="option">查询选项</param>
/// <returns>查询到的数据</returns>
Task<QueryData<RpcLog>> PageAsync(QueryPageOptions option);
Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option);
/// <summary>
/// 按天统计 RpcLog 数据
/// </summary>
/// <param name="day">统计的天数</param>
/// <returns>按天统计的结果列表</returns>
Task<List<RpcLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day);
}

View File

@@ -21,7 +21,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
/// <summary>
/// 最新十条
/// </summary>
public async Task<List<RpcLog>> GetNewLog()
public async Task<List<RpcLog>> GetNewRpcLog()
{
using var db = GetDB();
var data = await db.Queryable<RpcLog>().OrderByDescending(a => a.LogTime).Take(10).ToListAsync().ConfigureAwait(false);
@@ -32,7 +32,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
/// 表格查询
/// </summary>
/// <param name="option">查询条件</param>
public Task<QueryData<RpcLog>> PageAsync(QueryPageOptions option)
public Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option)
{
return QueryAsync(option);
}
@@ -51,7 +51,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
#endregion
public async Task<List<RpcLogDayStatisticsOutput>> StatisticsByDayAsync(int day)
public async Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day)
{
using var db = GetDB();
//取最近七天

View File

@@ -8,12 +8,11 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Gateway.Application;
public interface IGatewayMonitorHostedService : IHostedService
public interface IGatewayMonitorHostedService
{
public ILogger Logger { get; }
}

View File

@@ -0,0 +1,37 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
using ThingsGateway.Authentication;
namespace ThingsGateway.Gateway.Application;
internal sealed class AuthenticationService : IAuthenticationService
{
public Task<string> UUID() => Task.FromResult(ProAuthentication.UUID);
public Task<AuthorizeInfo> TryGetAuthorizeInfo()
{
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
return Task.FromResult(authorizeInfo);
}
public Task<AuthorizeInfo> TryAuthorize(string password)
{
ProAuthentication.TryAuthorize(password, out var authorizeInfo);
return Task.FromResult(authorizeInfo);
}
public Task UnAuthorize()
{
ProAuthentication.UnAuthorize();
return Task.CompletedTask;
}
}

View File

@@ -8,20 +8,14 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Authentication;
using System.Diagnostics.CodeAnalysis;
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.UpgradeServer;
public partial class NotFound404
public interface IAuthenticationService
{
[Inject]
[NotNull]
private IStringLocalizer<NotFound404>? Localizer { get; set; }
[Inject]
[NotNull]
private NavigationManager? NavigationManager { get; set; }
Task<string> UUID();
Task<AuthorizeInfo> TryAuthorize(string password);
Task<AuthorizeInfo> TryGetAuthorizeInfo();
Task UnAuthorize();
}

View File

@@ -0,0 +1,26 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
internal sealed class ChannelEnableService : IChannelEnableService
{
/// <summary>
/// 采集通道是否可用
/// </summary>
public Task<bool> StartCollectChannelEnable() => Task.FromResult(GlobalData.StartCollectChannelEnable);
/// <summary>
/// 业务通道是否可用
/// </summary>
public Task<bool> StartBusinessChannelEnable() => Task.FromResult(GlobalData.StartBusinessChannelEnable);
}

View File

@@ -0,0 +1,24 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
public interface IChannelEnableService
{
/// <summary>
/// 采集通道是否可用
/// </summary>
public Task<bool> StartCollectChannelEnable();
/// <summary>
/// 业务通道是否可用
/// </summary>
public Task<bool> StartBusinessChannelEnable();
}

View File

@@ -0,0 +1,152 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.Authentication;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
#if Management
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
#endif
public interface IManagementRpcServer : IRpcServer
{
[DmtpRpc]
Task<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas);
[DmtpRpc]
Task DeleteBackendLogAsync();
[DmtpRpc]
Task<List<BackendLog>> GetNewBackendLog();
[DmtpRpc]
Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option);
[DmtpRpc]
Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day);
/// <summary>
/// 删除 RpcLog 表中的所有记录
/// </summary>
/// <remarks>
/// 调用此方法会删除 RpcLog 表中的所有记录。
/// </remarks>
[DmtpRpc]
Task DeleteRpcLogAsync();
/// <summary>
/// 获取最新的十条 RpcLog 记录
/// </summary>
/// <returns>最新的十条记录</returns>
[DmtpRpc]
Task<List<RpcLog>> GetNewRpcLog();
/// <summary>
/// 分页查询 RpcLog 数据
/// </summary>
/// <param name="option">查询选项</param>
/// <returns>查询到的数据</returns>
[DmtpRpc]
Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option);
/// <summary>
/// 按天统计 RpcLog 数据
/// </summary>
/// <param name="day">统计的天数</param>
/// <returns>按天统计的结果列表</returns>
[DmtpRpc]
Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day);
[DmtpRpc]
Task RestartServer();
[DmtpRpc]
Task<string> UUID();
[DmtpRpc]
Task<AuthorizeInfo> TryAuthorize(string password);
[DmtpRpc]
Task<AuthorizeInfo> TryGetAuthorizeInfo();
[DmtpRpc]
Task UnAuthorize();
[DmtpRpc]
Task<bool> StartBusinessChannelEnable();
[DmtpRpc]
Task<bool> StartCollectChannelEnable();
[DmtpRpc]
Task StartRedundancyTaskAsync();
[DmtpRpc]
Task StopRedundancyTaskAsync();
[DmtpRpc]
Task RedundancyForcedSync();
[DmtpRpc]
public Task<TouchSocket.Core.LogLevel> RedundancyLogLevel();
[DmtpRpc]
public Task SetRedundancyLogLevel(TouchSocket.Core.LogLevel logLevel);
[DmtpRpc]
public Task<string> RedundancyLogPath();
/// <summary>
/// 修改冗余设置
/// </summary>
/// <param name="input"></param>
[DmtpRpc]
Task EditRedundancyOptionAsync(RedundancyOptions input);
/// <summary>
/// 获取冗余设置
/// </summary>
[DmtpRpc]
Task<RedundancyOptions> GetRedundancyAsync();
[DmtpRpc]
Task<OperResult<List<string>>> GetLogFiles(string directoryPath);
[DmtpRpc]
Task<OperResult<List<LogData>>> LastLogData(string file, int lineCount = 200);
/// <summary>
/// 根据插件类型获取信息
/// </summary>
/// <param name="pluginType"></param>
/// <returns></returns>
[DmtpRpc]
Task<List<PluginInfo>> GetPluginListAsync(PluginTypeEnum? pluginType = null);
/// <summary>
/// 分页显示插件
/// </summary>
[DmtpRpc]
public Task<QueryData<PluginInfo>> PluginPage(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null);
/// <summary>
/// 重载插件
/// </summary>
[DmtpRpc]
Task ReloadPlugin();
/// <summary>
/// 添加插件
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
[DmtpRpc]
Task SavePluginByPath(PluginAddPathInput plugin);
}

View File

@@ -11,21 +11,21 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
internal sealed class RemoteManagementHostedService : BackgroundService
internal sealed class ManagementHostedService : BackgroundService
{
public RemoteManagementHostedService(ILoggerFactory loggerFactory)
public ManagementHostedService(ILoggerFactory loggerFactory)
{
var clientManagementOptions = App.GetOptions<RemoteClientManagementOptions>();
var serverManagementOptions = App.GetOptions<RemoteServerManagementOptions>();
RemoteClientManagementTask = new RemoteManagementTask(loggerFactory.CreateLogger(nameof(RemoteClientManagementTask)), clientManagementOptions);
RemoteServerManagementTask = new RemoteManagementTask(loggerFactory.CreateLogger(nameof(RemoteServerManagementTask)), serverManagementOptions);
RemoteClientManagementTask = new ManagementTask(loggerFactory.CreateLogger(nameof(RemoteClientManagementTask)), clientManagementOptions);
RemoteServerManagementTask = new ManagementTask(loggerFactory.CreateLogger(nameof(RemoteServerManagementTask)), serverManagementOptions);
}
private RemoteManagementTask RemoteClientManagementTask;
private RemoteManagementTask RemoteServerManagementTask;
private ManagementTask RemoteClientManagementTask;
private ManagementTask RemoteServerManagementTask;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{

View File

@@ -12,8 +12,8 @@ using System.ComponentModel.DataAnnotations;
using ThingsGateway.ConfigurableOptions;
namespace ThingsGateway.Management;
public class RemoteManagementOptions
namespace ThingsGateway.Gateway.Application;
public class ManagementOptions
{
public bool Enable { get; set; }
@@ -33,11 +33,11 @@ public class RemoteManagementOptions
}
public class RemoteClientManagementOptions : RemoteManagementOptions, IConfigurableOptions
public class RemoteClientManagementOptions : ManagementOptions, IConfigurableOptions
{
public override bool IsServer => false;
}
public class RemoteServerManagementOptions : RemoteManagementOptions, IConfigurableOptions
public class RemoteServerManagementOptions : ManagementOptions, IConfigurableOptions
{
public override bool IsServer => true;
}

View File

@@ -0,0 +1,100 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.Authentication;
using TouchSocket.Core;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBackendLogService, IRpcLogService, IRestartService, IAuthenticationService, IChannelEnableService, IRedundancyHostedService, IRedundancyService, ITextFileReadService, IPluginPageService
{
[DmtpRpc]
public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync();
[DmtpRpc]
public Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day) => App.GetService<IBackendLogService>().BackendLogStatisticsByDayAsync(day);
[DmtpRpc]
public Task<List<BackendLog>> GetNewBackendLog() => App.GetService<IBackendLogService>().GetNewBackendLog();
[DmtpRpc]
public Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option) => App.GetService<IBackendLogService>().BackendLogPageAsync(option);
[DmtpRpc]
public async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas)
{
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync($"Management[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, callContext.Token).ConfigureAwait(false);
return data.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => b.Value.GetOperResult()));
}
public Task DeleteRpcLogAsync() => App.GetService<IRpcLogService>().DeleteRpcLogAsync();
public Task<List<RpcLog>> GetNewRpcLog() => App.GetService<IRpcLogService>().GetNewRpcLog();
public Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option) => App.GetService<IRpcLogService>().RpcLogPageAsync(option);
public Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day) => App.GetService<IRpcLogService>().RpcLogStatisticsByDayAsync(day);
public Task RestartServer() => App.GetService<IRestartService>().RestartServer();
public Task<string> UUID() => App.GetService<IAuthenticationService>().UUID();
public Task<AuthorizeInfo> TryAuthorize(string password) => App.GetService<IAuthenticationService>().TryAuthorize(password);
public Task<AuthorizeInfo> TryGetAuthorizeInfo() => App.GetService<IAuthenticationService>().TryGetAuthorizeInfo();
public Task UnAuthorize() => App.GetService<IAuthenticationService>().UnAuthorize();
public Task<bool> StartBusinessChannelEnable() => App.GetService<IChannelEnableService>().StartBusinessChannelEnable();
public Task<bool> StartCollectChannelEnable() => App.GetService<IChannelEnableService>().StartCollectChannelEnable();
public Task StartRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StartRedundancyTaskAsync();
public Task StopRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StopRedundancyTaskAsync();
public Task RedundancyForcedSync() => App.GetService<IRedundancyHostedService>().RedundancyForcedSync();
public Task EditRedundancyOptionAsync(RedundancyOptions input) => App.GetService<IRedundancyService>().EditRedundancyOptionAsync(input);
public Task<RedundancyOptions> GetRedundancyAsync() => App.GetService<IRedundancyService>().GetRedundancyAsync();
public Task<LogLevel> RedundancyLogLevel() => App.GetService<IRedundancyHostedService>().RedundancyLogLevel();
public Task SetRedundancyLogLevel(LogLevel logLevel) => App.GetService<IRedundancyHostedService>().SetRedundancyLogLevel(logLevel);
public Task<string> RedundancyLogPath() => App.GetService<IRedundancyHostedService>().RedundancyLogPath();
public Task<OperResult<List<string>>> GetLogFiles(string directoryPath) => App.GetService<ITextFileReadService>().GetLogFiles(directoryPath);
public Task<OperResult<List<LogData>>> LastLogData(string file, int lineCount = 200) => App.GetService<ITextFileReadService>().LastLogData(file, lineCount);
public Task<List<PluginInfo>> GetPluginListAsync(PluginTypeEnum? pluginType = null) => App.GetService<IPluginPageService>().GetPluginListAsync(pluginType);
public Task<QueryData<PluginInfo>> PluginPage(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null) => App.GetService<IPluginPageService>().PluginPage(options, pluginTypeEnum);
public Task ReloadPlugin() => App.GetService<IPluginPageService>().ReloadPlugin();
public Task SavePluginByPath(PluginAddPathInput plugin) => App.GetService<IPluginPageService>().SavePluginByPath(plugin);
}

View File

@@ -8,26 +8,26 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ThingsGateway.Gateway.Application;
using TouchSocket.Core;
using TouchSocket.Dmtp;
using TouchSocket.Dmtp.FileTransfer;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public partial class RemoteManagementTask : AsyncDisposableObject
public partial class ManagementTask : AsyncDisposableObject
{
internal const string LogPath = $"Logs/{nameof(RemoteManagementTask)}";
internal const string LogPath = $"Logs/{nameof(ManagementTask)}";
private ILog LogMessage;
private ILogger _logger;
private TextFileLogger TextLogger;
public RemoteManagementTask(ILogger logger, RemoteManagementOptions remoteManagementOptions)
public ManagementTask(ILogger logger, ManagementOptions managementOptions)
{
_logger = logger;
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
@@ -37,7 +37,7 @@ public partial class RemoteManagementTask : AsyncDisposableObject
log?.AddLogger(TextLogger);
LogMessage = log;
_remoteManagementOptions = remoteManagementOptions;
_managementOptions = managementOptions;
}
@@ -48,9 +48,9 @@ public partial class RemoteManagementTask : AsyncDisposableObject
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_remoteManagementOptions.Enable) return;
if (!_managementOptions.Enable) return;
if (_remoteManagementOptions.IsServer)
if (_managementOptions.IsServer)
{
_tcpDmtpService ??= await GetTcpDmtpService().ConfigureAwait(false);
}
@@ -78,33 +78,52 @@ public partial class RemoteManagementTask : AsyncDisposableObject
private TcpDmtpClient? _tcpDmtpClient;
private TcpDmtpService? _tcpDmtpService;
private RemoteManagementOptions _remoteManagementOptions;
private ManagementOptions _managementOptions;
private async Task<TcpDmtpClient> GetTcpDmtpClient()
{
var tcpDmtpClient = new TcpDmtpClient();
var config = new TouchSocketConfig()
.SetRemoteIPHost(_remoteManagementOptions.ServerUri)
.SetRemoteIPHost(_managementOptions.ServerUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
.SetDmtpOption(new DmtpOption() { VerifyToken = _remoteManagementOptions.VerifyToken })
.SetDmtpOption(new DmtpOption() { VerifyToken = _managementOptions.VerifyToken })
.ConfigureContainer(a =>
{
a.AddDmtpRouteService();//添加路由策略
a.AddLogger(LogMessage);
a.AddRpcStore(store => store.RegisterServer<RemoteManagementRpcServer>());
a.AddRpcStore(store =>
{
store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer());
store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer());
});
})
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseDmtpRpc();
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
{
b.UseSystemTextJson(json =>
{
});
});
a.UseDmtpFileTransfer();//必须添加文件传输插件
a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(_remoteManagementOptions.HeartbeatInterval))
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
a.AddDmtpHandshakedPlugin(async () =>
{
try
{
await tcpDmtpClient.ResetIdAsync($"{_remoteManagementOptions.Name}:{GlobalData.HardwareJob.HardwareInfo.UUID}").ConfigureAwait(false);
await tcpDmtpClient.ResetIdAsync($"{_managementOptions.Name}:{GlobalData.HardwareJob.HardwareInfo.UUID}").ConfigureAwait(false);
}
catch (Exception)
{
@@ -121,20 +140,37 @@ public partial class RemoteManagementTask : AsyncDisposableObject
{
var tcpDmtpService = new TcpDmtpService();
var config = new TouchSocketConfig()
.SetListenIPHosts(_remoteManagementOptions.ServerUri)
.SetListenIPHosts(_managementOptions.ServerUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
.SetDmtpOption(new DmtpOption() { VerifyToken = _remoteManagementOptions.VerifyToken })
.SetDmtpOption(new DmtpOption() { VerifyToken = _managementOptions.VerifyToken })
.ConfigureContainer(a =>
{
a.AddDmtpRouteService();//添加路由策略
a.AddLogger(LogMessage);
a.AddRpcStore(store => store.RegisterServer<RemoteManagementRpcServer>());
a.AddRpcStore(store =>
{
store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer());
store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer());
});
})
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseDmtpRpc();
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
{
b.UseSystemTextJson(json =>
{
});
});
a.UseDmtpFileTransfer();//必须添加文件传输插件
a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(_remoteManagementOptions.HeartbeatInterval))
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
});
@@ -144,7 +180,7 @@ public partial class RemoteManagementTask : AsyncDisposableObject
private async Task EnsureChannelOpenAsync(CancellationToken cancellationToken)
{
if (_remoteManagementOptions.IsServer)
if (_managementOptions.IsServer)
{
if (_tcpDmtpService.ServerState != ServerState.Running)
{
@@ -176,6 +212,18 @@ public partial class RemoteManagementTask : AsyncDisposableObject
_tcpDmtpService.SafeDispose();
_tcpDmtpService = null;
}
TextLogger?.Dispose();
await base.DisposeAsync(disposing).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace ThingsGateway.Gateway.Application;
public static class FileConst
{
public const string FilePathKey = "FilePath";
public static string UpgradePath = Path.Combine(AppContext.BaseDirectory, "Upgrade.zip");
public static string UpgradeBackupPath = Path.Combine(AppContext.BaseDirectory, "..", "Backup.zip");
public static string UpgradeBackupDirPath = Path.Combine(AppContext.BaseDirectory, "..", "Backup");
public const string UpdateZipFileServerDir = "UpdateZipFile";
}

View File

@@ -2,7 +2,7 @@
using TouchSocket.Dmtp;
using TouchSocket.Dmtp.FileTransfer;
namespace ThingsGateway.Upgrade;
namespace ThingsGateway.Gateway.Application;
internal sealed class FilePlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTransferredPlugin, IDmtpRoutingPlugin
{
@@ -67,7 +67,7 @@ internal sealed class FilePlugin : PluginBase, IDmtpFileTransferringPlugin, IDmt
public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e)
{
e.IsPermitOperation = true;//允许路由
m_logger.Info($"路由类型:{e.RouterType}");
m_logger.Debug($"路由类型:{e.RouterType}");
await e.InvokeNext().ConfigureAwait(false);
}
}

View File

@@ -2,7 +2,7 @@
using System.Runtime.InteropServices;
namespace ThingsGateway.Upgrade;
namespace ThingsGateway.Gateway.Application;
public class UpdateZipFile
{

View File

@@ -0,0 +1,108 @@
using TouchSocket.Core;
using TouchSocket.Dmtp;
using TouchSocket.Dmtp.FileTransfer;
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
public static class FileServerHelpers
{
/// <summary>
/// 传输限速
/// </summary>
public const long MaxSpeed = 1024 * 1024 * 10L;
/// <summary>
/// 客户端从服务器下载文件。
/// </summary>
public static async Task<bool> ClientPullFileFromService(IDmtpActor client, string path, string savePath)
{
Directory.CreateDirectory(savePath.AsFile().DirectoryName);
var metadata = new Metadata();//传递到服务器的元数据
metadata.Add(FileConst.FilePathKey, path);
var fileOperator = new FileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = savePath,//客户端本地保存路径
ResourcePath = path,//请求文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};
fileOperator.MaxSpeed = MaxSpeed;//设置最大限速。
//此处的作用相当于Timer定时每秒输出当前的传输进度和速度。
var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) =>
{
if (fileOperator.IsEnd)
{
loop.Dispose();
}
client.Logger.Info($"请求文件:{fileOperator.ResourcePath},进度:{(fileOperator.Progress * 100).ToString("F2")}%,速度:{(fileOperator.Speed() / 1024).ToString("F2")} KB/s");
});
_ = loopAction.RunAsync();
//此方法会阻塞直到传输结束也可以使用PullFileAsync
var result = await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator).ConfigureAwait(false);
if (result.IsSuccess)
client.Logger.Info(result.ToString());
else
client.Logger.Warning(result.ToString());
return result.IsSuccess;
}
/// <summary>
/// 客户端上传文件到服务器。
/// </summary>
public static async Task ClientPushFileFromService(IDmtpActor client, string resourcePath, string serverPath)
{
var metadata = new Metadata();//传递到服务器的元数据
metadata.Add(FileConst.FilePathKey, serverPath);
var fileOperator = new FileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = serverPath,//服务器本地保存路径
ResourcePath = resourcePath,//客户端本地即将上传文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};
fileOperator.MaxSpeed = MaxSpeed;//设置最大限速。
//此处的作用相当于Timer定时每秒输出当前的传输进度和速度。
var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) =>
{
if (fileOperator.IsEnd)
{
loop.Dispose();
}
client.Logger.Info($"进度:{(fileOperator.Progress * 100).ToString("F2")}%,速度:{(fileOperator.Speed() / 1024).ToString("F2")} KB/s");
});
_ = loopAction.RunAsync();
//此方法会阻塞直到传输结束也可以使用PushFileAsync
var result = await client.GetDmtpFileTransferActor().PushFileAsync(fileOperator).ConfigureAwait(false);
client.Logger.Info(result.ToString());
}
}

View File

@@ -12,13 +12,13 @@
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
#if Management
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
#endif
public interface IUpgradeRpcServer : IRpcServer
{
[DmtpRpc]
void Restart();
[DmtpRpc]
Task Upgrade();
Task Upgrade(ICallContext callContext, List<UpdateZipFile> updateZipFiles);
}

View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.NewLife;
using TouchSocket.Dmtp;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
public partial class UpgradeRpcServer : IRpcServer, IUpgradeRpcServer
{
[DmtpRpc]
public async Task Upgrade(ICallContext callContext, List<UpdateZipFile> updateZipFiles)
{
if (updateZipFiles?.Count > 0 && callContext.Caller is IDmtpActorObject dmtpActorObject)
await Update(dmtpActorObject.DmtpActor, updateZipFiles.OrderByDescending(a => a.Version).FirstOrDefault()).ConfigureAwait(false);
}
public static async Task Update(IDmtpActor dmtpActor, UpdateZipFile updateZipFile, Func<Task<bool>> check = null)
{
try
{
await UpdateWaitLock.WaitAsync().ConfigureAwait(false);
if (WaitLock.Waited)
{
throw new("Updating, please try again later");
}
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
RestartServerHelper.DeleteAndBackup();
var result = await FileServerHelpers.ClientPullFileFromService(dmtpActor, updateZipFile.FilePath, FileConst.UpgradePath).ConfigureAwait(false);
if (result)
{
if (check != null)
result = await check.Invoke().ConfigureAwait(false);
if (result)
{
RestartServerHelper.ExtractUpdate();
}
}
}
finally
{
WaitLock.Release();
}
}
finally
{
UpdateWaitLock.Release();
}
}
private static readonly WaitLock WaitLock = new(nameof(ManagementTask));
private static readonly WaitLock UpdateWaitLock = new(nameof(ManagementTask));
}

View File

@@ -15,10 +15,10 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Threading;
using ThingsGateway.Upgrade;
namespace ThingsGateway;
@@ -29,8 +29,8 @@ public static class RestartServerHelper
//删除不必要的文件
DeleteDelEx();
//删除备份
Delete(FileConst.BackupPath);
Delete(FileConst.BackupDirPath);
Delete(FileConst.UpgradeBackupPath);
Delete(FileConst.UpgradeBackupDirPath);
Delete(FileConst.UpgradePath);
//备份原数据
@@ -234,8 +234,8 @@ public static class RestartServerHelper
{
//备份原数据
var backupDir = new DirectoryInfo(AppContext.BaseDirectory);
backupDir.CopyTo(FileConst.BackupDirPath, allSub: true);
FileConst.BackupDirPath.AsDirectory().Compress(FileConst.BackupPath);
backupDir.CopyTo(FileConst.UpgradeBackupDirPath, allSub: true);
FileConst.UpgradeBackupDirPath.AsDirectory().Compress(FileConst.UpgradeBackupPath);
}
catch
{

View File

@@ -8,9 +8,7 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using ThingsGateway.Gateway.Application;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public class GatewayRedundantSerivce : IGatewayRedundantSerivce
{

View File

@@ -8,16 +8,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Management;
public interface IRedundancyHostedService : IHostedService
public interface IRedundancyHostedService
{
Task StartTaskAsync(CancellationToken cancellationToken);
Task StopTaskAsync();
Task ForcedSync(CancellationToken cancellationToken = default);
Task StartRedundancyTaskAsync();
Task StopRedundancyTaskAsync();
Task RedundancyForcedSync();
public TextFileLogger TextLogger { get; }
public string LogPath { get; }
public Task<TouchSocket.Core.LogLevel> RedundancyLogLevel();
public Task SetRedundancyLogLevel(TouchSocket.Core.LogLevel logLevel);
public Task<string> RedundancyLogPath();
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public interface IRedundancyService
{

View File

@@ -8,12 +8,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Gateway.Application;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
internal interface IRedundantRpcServer : IRpcServer
@@ -26,13 +24,3 @@ internal interface IRedundantRpcServer : IRpcServer
[DmtpRpc]
void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas);
}
//internal static partial class RedundantRpcServerExtensions
//{
// public static Task<Dictionary<string, Dictionary<string, OperResult<object>>>> RpcAsync<TClient>(this TClient client, System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, string>> deviceDatas, IInvokeOption invokeOption = default)
// where TClient : TouchSocket.Rpc.IRpcClient, TouchSocket.Dmtp.Rpc.IDmtpRpcActor
// {
// return client.InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>($"{nameof(ThingsGateway.Management)}{nameof(ThingsGateway.Management)}{nameof(ThingsGateway.Management.IRedundantRpcServer)}{nameof(ThingsGateway.Management.IRedundantRpcServer.Rpc)}", invokeOption, deviceDatas);
// }
//}

View File

@@ -10,9 +10,7 @@
using Newtonsoft.Json;
using ThingsGateway.Gateway.Application;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public class DeviceDataWithValue
{

View File

@@ -10,9 +10,7 @@
using Newtonsoft.Json;
using ThingsGateway.Gateway.Application;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public class VariableDataWithValue
{

View File

@@ -11,7 +11,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService
{
@@ -24,23 +24,37 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
}
private RedundancyTask RedundancyTask;
public TextFileLogger TextLogger => RedundancyTask.TextLogger;
public string LogPath => RedundancyTask.LogPath;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield();
await RedundancyTask.StartTaskAsync(stoppingToken).ConfigureAwait(false);
await RedundancyTask.StartRedundancyTaskAsync(stoppingToken).ConfigureAwait(false);
}
public Task StartTaskAsync(CancellationToken cancellationToken) => RedundancyTask.StartTaskAsync(cancellationToken);
public Task StopTaskAsync() => RedundancyTask.StopTaskAsync();
public Task ForcedSync(CancellationToken cancellationToken = default) => RedundancyTask.ForcedSync(cancellationToken);
public Task StartRedundancyTaskAsync() => RedundancyTask.StartRedundancyTaskAsync();
public Task StopRedundancyTaskAsync() => RedundancyTask.StopRedundancyTaskAsync();
public Task RedundancyForcedSync() => RedundancyTask.RedundancyForcedSync();
public override async Task StopAsync(CancellationToken cancellationToken)
{
await RedundancyTask.DisposeAsync().ConfigureAwait(false);
await base.StopAsync(cancellationToken).ConfigureAwait(false);
}
public Task<TouchSocket.Core.LogLevel> RedundancyLogLevel()
{
return Task.FromResult(RedundancyTask.TextLogger.LogLevel);
}
public Task SetRedundancyLogLevel(TouchSocket.Core.LogLevel logLevel)
{
RedundancyTask.TextLogger.LogLevel = logLevel;
return Task.CompletedTask;
}
public Task<string> RedundancyLogPath()
{
return Task.FromResult(RedundancyTask.LogPath);
}
}

View File

@@ -10,7 +10,7 @@
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 冗余配置

View File

@@ -9,7 +9,7 @@
//------------------------------------------------------------------------------
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
internal sealed class RedundancyService : BaseService<SysDict>, IRedundancyService
{

View File

@@ -14,7 +14,6 @@ using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using ThingsGateway.Extension.Generic;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
using TouchSocket.Core;
@@ -24,7 +23,7 @@ using TouchSocket.Rpc;
using TouchSocket.Rpc.Generators;
using TouchSocket.Sockets;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
{
@@ -238,9 +237,9 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
return GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.ReadOnlyIdChannels.Values);
}
public async Task StartTaskAsync(CancellationToken cancellationToken)
public async Task StartRedundancyTaskAsync(CancellationToken cancellationToken = default)
{
await StopTaskAsync().ConfigureAwait(false);
await StopRedundancyTaskAsync().ConfigureAwait(false);
RedundancyOptions = (await _redundancyService.GetRedundancyAsync().ConfigureAwait(false)).AdaptRedundancyOptions();
if (RedundancyOptions?.Enable == true)
@@ -278,7 +277,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
scheduledTask.Start();
}
}
public async Task StopTaskAsync()
public async Task StopRedundancyTaskAsync()
{
if (scheduledTask?.Enable == true)
{
@@ -310,7 +309,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
public async ValueTask DisposeAsync()
{
await StopTaskAsync().ConfigureAwait(false);
await StopRedundancyTaskAsync().ConfigureAwait(false);
TextLogger?.TryDispose();
scheduledTask?.SafeDispose();
@@ -336,13 +335,22 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
.ConfigureContainer(a =>
{
a.AddLogger(LogMessage);
a.AddRpcStore(store => store.RegisterServer(typeof(IRedundantRpcServer), new RedundantRpcServer(this)));
a.AddRpcStore(store =>
{
store.RegisterServer<IRedundantRpcServer>(new RedundantRpcServer(this));
});
})
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseDmtpRpc();
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
{
b.UseSystemTextJson(json =>
{
});
});
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
.SetMaxFailCount(redundancy.MaxErrorCount);
@@ -367,12 +375,21 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
.ConfigureContainer(a =>
{
a.AddLogger(LogMessage);
a.AddRpcStore(store => store.RegisterServer(typeof(IRedundantRpcServer), new RedundantRpcServer(this)));
a.AddRpcStore(store =>
{
store.RegisterServer<IRedundantRpcServer>(new RedundantRpcServer(this));
});
})
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseDmtpRpc();
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
{
b.UseSystemTextJson(json =>
{
});
});
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
.SetMaxFailCount(redundancy.MaxErrorCount);
@@ -396,10 +413,10 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
#endregion
#region ForcedSync
#region RedundancyForcedSync
WaitLock ForcedSyncWaitLock = new WaitLock(nameof(RedundancyTask));
public async Task ForcedSync(CancellationToken cancellationToken = default)
public async Task RedundancyForcedSync(CancellationToken cancellationToken = default)
{
await ForcedSyncWaitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -420,7 +437,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
if (!online)
{
LogMessage?.LogWarning("ForcedSync data error, no client online");
LogMessage?.LogWarning("RedundancyForcedSync data error, no client online");
return;
}
@@ -439,7 +456,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
catch (OperationCanceledException) { }
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "ForcedSync data error");
LogMessage?.LogWarning(ex, "RedundancyForcedSync data error");
}
}
finally
@@ -488,7 +505,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
await client.GetDmtpRpcActor().SyncDataAsync(channelBatch.ToList(), deviceBatch.ToList(), variableBatch, invokeOption).ConfigureAwait(false);
}
LogMessage?.LogTrace($"ForcedSync data success");
LogMessage?.LogTrace($"RedundancyForcedSync data success");
}
#endregion

View File

@@ -8,14 +8,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Gateway.Application;
using TouchSocket.Core;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
internal sealed partial class RedundantRpcServer : SingletonRpcServer, IRedundantRpcServer
{

View File

@@ -1,45 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.Gateway.Application;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Management;
public partial class RemoteManagementRpcServer : SingletonRpcServer,
IBackendLogService
{
[DmtpRpc]
public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync();
[DmtpRpc]
public Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day) => App.GetService<IBackendLogService>().StatisticsByDayAsync(day);
[DmtpRpc]
public Task<List<BackendLog>> GetNewLog() => App.GetService<IBackendLogService>().GetNewLog();
[DmtpRpc]
public Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option) => App.GetService<IBackendLogService>().PageAsync(option);
[DmtpRpc]
public async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
{
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync($"RemoteManagement[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, cancellationToken).ConfigureAwait(false);
return data.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => b.Value.GetOperResult()));
}
}

View File

@@ -8,11 +8,9 @@
// QQ群605534569
//------------------------------------------------------------------------------
global using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
global using Microsoft.AspNetCore.Components;
global using Microsoft.Extensions.Localization;
global using System.Diagnostics.CodeAnalysis;
global using ThingsGateway.Razor;
public interface IRestartService
{
Task RestartServer();
}

View File

@@ -0,0 +1,23 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
internal sealed class RestartService : IRestartService
{
public Task RestartServer()
{
RestartServerHelper.RestartServer();
return Task.CompletedTask;
}
}

View File

@@ -1,17 +0,0 @@
using Microsoft.Extensions.Hosting;
using ThingsGateway.Upgrade;
using TouchSocket.Dmtp;
namespace ThingsGateway.Management;
public interface IUpdateZipFileHostedService : IHostedService
{
TextFileLogger TextLogger { get; }
string LogPath { get; }
TcpDmtpClient? TcpDmtpClient { get; set; }
Task<List<UpdateZipFile>> GetList();
Task Update(UpdateZipFile updateZipFile, Func<Task<bool>> check = null);
}

View File

@@ -1,331 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Reflection;
using System.Runtime.InteropServices;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Log;
using ThingsGateway.Upgrade;
using TouchSocket.Core;
using TouchSocket.Dmtp;
using TouchSocket.Dmtp.FileTransfer;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
using TouchSocket.Rpc.Generators;
using TouchSocket.Sockets;
namespace ThingsGateway.Management;
internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZipFileHostedService
{
public UpdateZipFileHostedService(ILogger<UpdateZipFileHostedService> logger, INoticeService noticeService, IVerificatInfoService verificatInfoService)
{
NoticeService = noticeService;
VerificatInfoService = verificatInfoService;
_logger = logger;
Localizer = App.CreateLocalizerByType(typeof(UpdateZipFileHostedService));
// 创建新的文件日志记录器,并设置日志级别为 Trace
LogPath = "Logs/UpgradeLog";
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
TextLogger.LogLevel = TouchSocket.Core.LogLevel.Trace;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield();
TcpDmtpClient = await GetTcpDmtpClient().ConfigureAwait(false);
var upgradeServerOptions = App.GetOptions<UpgradeServerOptions>();
bool success = false;
bool first = true;
while (!success)
{
try
{
if (upgradeServerOptions.Enable)
{
await TcpDmtpClient.ConnectAsync().ConfigureAwait(false);
await TcpDmtpClient.ResetIdAsync(upgradeServerOptions.Id).ConfigureAwait(false);
success = true;
}
}
catch (Exception ex)
{
if (first)
XTrace.WriteException(ex);
first = false;
}
finally
{
await Task.Delay(3000, stoppingToken).ConfigureAwait(false);
}
}
while (!stoppingToken.IsCancellationRequested)
{
try
{
if (upgradeServerOptions.Enable && !App.HostEnvironment.IsDevelopment())
{
var data = await GetList().ConfigureAwait(false);
if (data.Count != 0 && (LastVersion == null || data.FirstOrDefault().Version > LastVersion))
{
LastVersion = data.FirstOrDefault().Version;
var verificatInfoIds = VerificatInfoService.GetListByUserId(RoleConst.SuperAdminId);
await NoticeService.NavigationMesage(verificatInfoIds.Where(a => a.Online).SelectMany(a => a.ClientIds), "/gateway/system?tab=1", App.CreateLocalizerByType(typeof(UpdateZipFileHostedService))["Update"]).ConfigureAwait(false);
}
}
error = false;
await Task.Delay(60000, stoppingToken).ConfigureAwait(false);
}
catch (TaskCanceledException) { }
catch (OperationCanceledException) { }
catch (Exception ex)
{
if (!error)
TextLogger.LogWarning(ex);
error = true;
}
}
}
private INoticeService NoticeService { get; set; }
private IVerificatInfoService VerificatInfoService { get; set; }
private Version LastVersion;
private bool error;
private ILogger<UpdateZipFileHostedService> _logger;
private IStringLocalizer Localizer;
private LoggerGroup _log { get; set; }
public TextFileLogger TextLogger { get; }
public string LogPath { get; }
/// <summary>
/// 传输限速
/// </summary>
public const long MaxSpeed = 1024 * 1024 * 10L;
public TcpDmtpClient? TcpDmtpClient { get; set; }
public async Task<List<UpdateZipFile>> GetList()
{
var upgradeServerOptions = App.GetOptions<UpgradeServerOptions>();
if (!upgradeServerOptions.Enable)
throw new Exception("Update service not enabled");
//设置调用配置
var tokenSource = new CancellationTokenSource();//可取消令箭源可用于取消Rpc的调用
var invokeOption = new DmtpInvokeOption()//调用配置
{
FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型
SerializationType = SerializationType.Json,//序列化类型
Timeout = 5000,//调用超时设置
Token = tokenSource.Token//配置可取消令箭
};
var updateZipFiles = await TcpDmtpClient.GetDmtpRpcActor().GetListAsync(new UpdateZipFileInput()
{
Version = Assembly.GetEntryAssembly().GetName().Version,
DotNetVersion = Environment.Version,
OSPlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "Linux" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "OSX" : "Unknown",
Architecture = RuntimeInformation.ProcessArchitecture,
AppName = "ThingsGateway"
}, invokeOption).ConfigureAwait(false);
return updateZipFiles.OrderByDescending(a => a.Version).ToList();
}
private readonly WaitLock WaitLock = new(nameof(UpdateZipFileHostedService));
private readonly WaitLock UpdateWaitLock = new(nameof(UpdateZipFileHostedService));
public async Task Update(UpdateZipFile updateZipFile, Func<Task<bool>> check = null)
{
try
{
await UpdateWaitLock.WaitAsync().ConfigureAwait(false);
var upgradeServerOptions = App.GetOptions<UpgradeServerOptions>();
if (!upgradeServerOptions.Enable)
return;
if (WaitLock.Waited)
{
_log.LogWarning("Updating, please try again later");
return;
}
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
RestartServerHelper.DeleteAndBackup();
var result = await ClientPullFileFromService(TcpDmtpClient, updateZipFile.FilePath).ConfigureAwait(false);
if (result)
{
if (check != null)
result = await check.Invoke().ConfigureAwait(false);
if (result)
{
RestartServerHelper.ExtractUpdate();
}
}
}
finally
{
WaitLock.Release();
}
}
catch (Exception ex)
{
_log.LogWarning(ex);
}
finally
{
UpdateWaitLock.Release();
}
}
/// <summary>
/// 客户端从服务器下载文件。
/// </summary>
private static async Task<bool> ClientPullFileFromService(TcpDmtpClient client, string path)
{
var metadata = new Metadata();//传递到服务器的元数据
metadata.Add(FileConst.FilePathKey, path);
var fileOperator = new FileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = FileConst.UpgradePath,//客户端本地保存路径
ResourcePath = path,//请求文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};
fileOperator.MaxSpeed = MaxSpeed;//设置最大限速。
//此处的作用相当于Timer定时每秒输出当前的传输进度和速度。
var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) =>
{
if (fileOperator.IsEnd)
{
loop.Dispose();
}
client.Logger.Info($"请求文件:{fileOperator.ResourcePath},进度:{(fileOperator.Progress * 100).ToString("F2")}%,速度:{(fileOperator.Speed() / 1024).ToString("F2")} KB/s");
});
_ = loopAction.RunAsync();
//此方法会阻塞直到传输结束也可以使用PullFileAsync
var result = await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator).ConfigureAwait(false);
if (result.IsSuccess)
client.Logger.Info(result.ToString());
else
client.Logger.Warning(result.ToString());
return result.IsSuccess;
}
/// <summary>
/// 客户端上传文件到服务器。
/// </summary>
private static async Task ClientPushFileFromService(TcpDmtpClient client, string serverPath, string resourcePath)
{
var metadata = new Metadata();//传递到服务器的元数据
metadata.Add(FileConst.FilePathKey, serverPath);
var fileOperator = new FileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = serverPath,//服务器本地保存路径
ResourcePath = resourcePath,//客户端本地即将上传文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};
fileOperator.MaxSpeed = MaxSpeed;//设置最大限速。
//此处的作用相当于Timer定时每秒输出当前的传输进度和速度。
var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) =>
{
if (fileOperator.IsEnd)
{
loop.Dispose();
}
client.Logger.Info($"进度:{(fileOperator.Progress * 100).ToString("F2")}%,速度:{(fileOperator.Speed() / 1024).ToString("F2")} KB/s");
});
_ = loopAction.RunAsync();
//此方法会阻塞直到传输结束也可以使用PushFileAsync
var result = await client.GetDmtpFileTransferActor().PushFileAsync(fileOperator).ConfigureAwait(false);
client.Logger.Info(result.ToString());
}
/// <summary>
/// 底层错误日志输出
/// </summary>
internal void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4)
{
_logger?.Log_Out(arg1, arg2, arg3, arg4);
}
private async Task<TcpDmtpClient> GetTcpDmtpClient()
{
_log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
_log.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
_log.AddLogger(TextLogger);
var upgradeServerOptions = App.GetOptions<UpgradeServerOptions>();
var config = new TouchSocketConfig()
.SetRemoteIPHost(new IPHost($"{upgradeServerOptions.UpgradeServerIP}:{upgradeServerOptions.UpgradeServerPort}"))
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
.SetDmtpOption(new DmtpOption()
{
VerifyToken = upgradeServerOptions.VerifyToken
})
.ConfigureContainer(a =>
{
a.AddLogger(_log);
var rpcServer = new UpgradeRpcServer();
a.AddRpcStore(store => store.RegisterServer<IUpgradeRpcServer>(rpcServer));
a.AddDmtpRouteService();//添加路由策略
})
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseDmtpRpc();
a.UseDmtpFileTransfer();//必须添加文件传输插件
a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromSeconds(5))
.SetMaxFailCount(3);
});
TcpDmtpClient client = new();
await client.SetupAsync(config).ConfigureAwait(false);
return client;
}
public override void Dispose()
{
base.Dispose();
TextLogger.Dispose();
}
}

View File

@@ -1,31 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Management;
public partial class UpgradeRpcServer : SingletonRpcServer, IUpgradeRpcServer
{
[DmtpRpc]
public void Restart()
{
RestartServerHelper.RestartServer();
}
[DmtpRpc]
public async Task Upgrade()
{
var _updateZipFileService = App.GetService<IUpdateZipFileHostedService>();
var data = await _updateZipFileService.GetList().ConfigureAwait(false);
if (data.Count != 0)
await _updateZipFileService.Update(data.OrderByDescending(a => a.Version).FirstOrDefault()).ConfigureAwait(false);
}
}

View File

@@ -11,7 +11,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
internal sealed class WebApiHostedService : BackgroundService
{

View File

@@ -12,7 +12,7 @@ using System.ComponentModel.DataAnnotations;
using ThingsGateway.ConfigurableOptions;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public class WebApiOptions : IConfigurableOptions
{
public bool Enable { get; set; }

View File

@@ -12,15 +12,13 @@ using Microsoft.Extensions.Logging;
using System.Text;
using ThingsGateway.Gateway.Application;
using TouchSocket.Core;
using TouchSocket.Http;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
using TouchSocket.WebApi.Swagger;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
public partial class WebApiTask : AsyncDisposableObject
{
@@ -143,6 +141,7 @@ public partial class WebApiTask : AsyncDisposableObject
_httpService = null;
}
await base.DisposeAsync(disposing).ConfigureAwait(false);
TextLogger?.Dispose();
}
}
/// <summary>

View File

@@ -30,3 +30,20 @@ public class PluginAddInput
/// </summary>
public List<IBrowserFile> OtherFiles { get; set; }
}
/// <summary>
/// 插件添加DTO
/// </summary>
public class PluginAddPathInput
{
/// <summary>
/// 主程序集
/// </summary>
[Required]
public string MainFilePath { get; set; }
/// <summary>
/// 附属程序集
/// </summary>
public List<string> OtherFilePaths { get; set; } = new();
}

View File

@@ -0,0 +1,52 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 驱动插件服务
/// </summary>
public interface IPluginPageService
{
/// <summary>
/// 根据插件类型获取信息
/// </summary>
/// <param name="pluginType"></param>
/// <returns></returns>
Task<List<PluginInfo>> GetPluginListAsync(PluginTypeEnum? pluginType = null);
/// <summary>
/// 分页显示插件
/// </summary>
public Task<QueryData<PluginInfo>> PluginPage(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null);
/// <summary>
/// 重载插件
/// </summary>
Task ReloadPlugin();
///// <summary>
///// 添加插件
///// </summary>
///// <param name="plugin"></param>
///// <returns></returns>
//Task SavePlugin(PluginAddInput plugin);
/// <summary>
/// 添加插件
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
Task SavePluginByPath(PluginAddPathInput plugin);
}

View File

@@ -15,11 +15,20 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 驱动插件服务
/// </summary>
public interface IPluginService
public interface IPluginService : IPluginPageService
{
Type GetDebugUI(string pluginName);
Type GetAddressUI(string pluginName);
/// <summary>
/// 根据插件类型获取信息
/// </summary>
/// <param name="pluginType"></param>
/// <returns></returns>
List<PluginInfo> GetPluginListSync(PluginTypeEnum? pluginType = null);
/// <summary>
/// 根据插件全名称构建插件实例
/// </summary>
@@ -40,12 +49,7 @@ public interface IPluginService
/// <returns></returns>
(IEnumerable<IEditorItem> EditorItems, object Model, Type PropertyUIType) GetDriverPropertyTypes(string pluginName, IDriver? driver = null);
/// <summary>
/// 根据插件类型获取信息
/// </summary>
/// <param name="pluginType"></param>
/// <returns></returns>
List<PluginInfo> GetList(PluginTypeEnum? pluginType = null);
/// <summary>
/// 获取变量属性
@@ -55,22 +59,6 @@ public interface IPluginService
/// <returns></returns>
(IEnumerable<IEditorItem> EditorItems, object Model, Type VariablePropertyUIType) GetVariablePropertyTypes(string pluginName, BusinessBase? businessBase = null);
/// <summary>
/// 分页显示插件
/// </summary>
public QueryData<PluginInfo> Page(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null);
/// <summary>
/// 重载插件
/// </summary>
void Reload();
/// <summary>
/// 添加插件
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
Task SavePlugin(PluginAddInput plugin);
/// <summary>
/// 设置插件动态属性

View File

@@ -0,0 +1,134 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class PluginInfoUtil
{
public const string TempDirName = "TempGatewayPlugins";
private static WaitLock _locker = new(nameof(PluginInfoUtil));
/// <summary>
/// 异步保存驱动程序信息。
/// </summary>
public static async Task SavePlugin(PluginAddInput plugin)
{
try
{
// 等待锁可用
await _locker.WaitAsync().ConfigureAwait(false);
var maxFileSize = 100 * 1024 * 1024; // 最大100MB
string tempDir = TempDirName;
// 获取主程序集文件名
var mainFileName = Path.GetFileNameWithoutExtension(plugin.MainFile.Name);
string fullDir = string.Empty;
//判定是否上下文程序集
// 构建插件文件夹绝对路径
fullDir = AppContext.BaseDirectory.CombinePathWithOs(tempDir, mainFileName);
PluginAddPathInput pluginAddPathInput = new();
try
{
// 构建主程序集绝对路径
var fullPath = fullDir.CombinePathWithOs(plugin.MainFile.Name);
// 获取主程序集文件流
using var stream = plugin.MainFile.OpenReadStream(maxFileSize);
using FileStream fs = new($"{fullPath}", FileMode.Create);
await stream.CopyToAsync(fs).ConfigureAwait(false);
pluginAddPathInput.MainFilePath = fullPath;
foreach (var item in plugin.OtherFiles ?? new())
{
// 获取附属文件流
using var otherStream = item.OpenReadStream(maxFileSize);
var otherFullPath = $"{fullDir.CombinePathWithOs(item.Name)}";
using FileStream otherFs = new(otherFullPath, FileMode.Create);
await stream.CopyToAsync(otherFs).ConfigureAwait(false);
pluginAddPathInput.OtherFilePaths.Add(otherFullPath);
}
await App.GetService<IPluginPageService>().SavePluginByPath(pluginAddPathInput).ConfigureAwait(false);
}
finally
{
if (File.Exists(pluginAddPathInput.MainFilePath))
{
File.Delete(pluginAddPathInput.MainFilePath);
}
foreach (var item in pluginAddPathInput.OtherFilePaths)
{
if (File.Exists(item))
{
File.Delete(item);
}
}
}
}
finally
{
// 释放锁资源
_locker.Release();
}
}
/// <summary>
/// 根据插件FullName获取插件主程序集名称和插件类型名称
/// </summary>
/// <param name="pluginName"></param>
/// <returns></returns>
public static (string FileName, string TypeName) GetFileNameAndTypeName(string pluginName)
{
if (pluginName.IsNullOrWhiteSpace())
return (string.Empty, string.Empty);
// 查找最后一个 '.' 的索引
int lastIndex = pluginName.LastIndexOf('.');
// 如果找到了最后一个 '.',并且它不是最后一个字符
if (lastIndex != -1 && lastIndex < pluginName.Length - 1)
{
// 获取子串直到最后一个 '.'
string part1 = pluginName.Substring(0, lastIndex);
// 获取最后一个 '.' 后面的部分
string part2 = pluginName.Substring(lastIndex + 1);
return (part1, part2);
}
else
{
// 如果没有找到 '.',或者 '.' 是最后一个字符,则返回默认的键和插件名称
return (nameof(ThingsGateway), pluginName);
}
}
/// <summary>
/// 根据插件主程序集名称和插件类型名称获取插件FullName
/// </summary>
/// <param name="fileName"></param>
/// <param name="name"></param>
/// <returns></returns>
public static string GetFullName(string fileName, string name)
{
return string.IsNullOrEmpty(fileName) ? name : $"{fileName}.{name}";
}
}

View File

@@ -35,7 +35,7 @@ internal sealed class PluginService : IPluginService
/// </summary>
public const string DirName = "GatewayPlugins";
private const string CacheKeyGetPluginOutputs = $"ThingsGateway.Gateway.Application.{nameof(PluginService)}{nameof(GetList)}";
private const string CacheKeyGetPluginOutputs = $"ThingsGateway.Gateway.Application.{nameof(PluginService)}{nameof(GetPluginListSync)}";
private const string SaveEx = ".save";
private const string DelEx = ".del";
@@ -69,6 +69,7 @@ internal sealed class PluginService : IPluginService
/// </summary>
private System.Collections.ObjectModel.ReadOnlyDictionary<string, Type> _defaultDriverBaseDict { get; }
/// <summary>
/// 插件FullName/插件Type
/// </summary>
@@ -100,7 +101,7 @@ internal sealed class PluginService : IPluginService
_locker.Wait();
// 解析插件名称,获取文件名和类型名
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(pluginName);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(pluginName);
// 如果是默认键,则搜索主程序上下文中的类型
if (_defaultDriverBaseDict.TryGetValue(pluginName, out var type))
@@ -258,7 +259,29 @@ internal sealed class PluginService : IPluginService
/// </summary>
/// <param name="pluginType">要筛选的插件类型,可选参数</param>
/// <returns>符合条件的插件列表</returns>
public List<PluginInfo> GetList(PluginTypeEnum? pluginType = null)
public Task<List<PluginInfo>> GetPluginListAsync(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> GetPluginListSync(PluginTypeEnum? pluginType = null)
{
// 获取完整的插件列表
var pluginList = PrivateGetList();
@@ -274,7 +297,6 @@ internal sealed class PluginService : IPluginService
return filteredPlugins;
}
/// <summary>
/// 获取变量的属性类型
/// </summary>
@@ -308,21 +330,21 @@ internal sealed class PluginService : IPluginService
/// <summary>
/// 分页显示插件
/// </summary>
public QueryData<PluginInfo> Page(QueryPageOptions options, PluginTypeEnum? pluginType = null)
public async Task<QueryData<PluginInfo>> PluginPage(QueryPageOptions options, PluginTypeEnum? pluginType = null)
{
//指定关键词搜索为插件FullName
var query = GetList(pluginType).WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.FullName.Contains(options.SearchText)).GetQueryData(options);
var query = (await GetPluginListAsync(pluginType).ConfigureAwait(false)).WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.FullName.Contains(options.SearchText)).GetQueryData(options);
return query;
}
/// <summary>
/// 移除全部插件
/// </summary>
public void Reload()
public async Task ReloadPlugin()
{
try
{
_locker.Wait();
await _locker.WaitAsync().ConfigureAwait(false);
_driverBaseDict.Clear();
foreach (var item in _assemblyLoadContextDict)
{
@@ -335,53 +357,56 @@ internal sealed class PluginService : IPluginService
{
_locker.Release();
}
}
private WaitLock SaveLock = new(nameof(PluginService));
/// <summary>
/// 异步保存驱动程序信息。
/// </summary>
/// <param name="plugin">要保存的插件信息。</param>
[OperDesc("SavePlugin", isRecordPar: false, localizerType: typeof(PluginAddInput))]
public async Task SavePlugin(PluginAddInput plugin)
public async Task SavePluginByPath(PluginAddPathInput plugin)
{
try
{
// 等待锁可用
await _locker.WaitAsync().ConfigureAwait(false);
await SaveLock.WaitAsync().ConfigureAwait(false);
var tempDir = DirName;
// 创建程序集加载上下文
var assemblyLoadContext = new AssemblyLoadContext(CommonUtils.GetSingleId().ToString(), true);
// 存储其他文件的内存流列表
List<(string Name, MemoryStream MemoryStream)> otherFilesStreams = new();
var maxFileSize = 100 * 1024 * 1024; // 最大100MB
// 获取主程序集文件名
var mainFileName = Path.GetFileNameWithoutExtension(plugin.MainFile.Name);
var mainFileName = Path.GetFileNameWithoutExtension(plugin.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 = PluginServiceUtil.GetFileNameAndTypeName(defaultDriver.Key);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(defaultDriver.Key);
fullDir = Path.GetDirectoryName(filtResult.FileName);
isDefaultDriver = true;
}
else
{
// 构建插件文件夹绝对路径
fullDir = AppContext.BaseDirectory.CombinePathWithOs(DirName, mainFileName);
fullDir = AppContext.BaseDirectory.CombinePathWithOs(tempDir, mainFileName);
isDefaultDriver = false;
}
// 获取主程序集文件流
MemoryStream mainMemoryStream = new MemoryStream();
try
{
// 构建主程序集绝对路径
var fullPath = fullDir.CombinePathWithOs(plugin.MainFile.Name);
var fullPath = fullDir.CombinePathWithOs(Path.GetFileName(plugin.MainFilePath));
// 获取主程序集文件流
using var stream = plugin.MainFile.OpenReadStream(maxFileSize);
MemoryStream mainMemoryStream = new MemoryStream();
using var stream = File.Open(plugin.MainFilePath, FileMode.Open, FileAccess.Read);
await stream.CopyToAsync(mainMemoryStream).ConfigureAwait(false);
mainMemoryStream.Seek(0, SeekOrigin.Begin);
@@ -389,14 +414,14 @@ internal sealed class PluginService : IPluginService
// 先加载到内存,如果成功添加后再装载到文件
// 加载主程序集
var assembly = assemblyLoadContext.LoadFromStream(mainMemoryStream);
foreach (var item in plugin.OtherFiles ?? new())
foreach (var item in plugin.OtherFilePaths ?? new())
{
// 获取附属文件流
using var otherStream = item.OpenReadStream(maxFileSize);
using var otherStream = File.Open(plugin.MainFilePath, FileMode.Open, FileAccess.Read);
MemoryStream memoryStream = new MemoryStream();
await otherStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Seek(0, SeekOrigin.Begin);
otherFilesStreams.Add((item.Name, memoryStream));
otherFilesStreams.Add((Path.GetFileName(item), memoryStream));
try
{
// 尝试加载附属程序集
@@ -451,6 +476,16 @@ internal sealed class PluginService : IPluginService
}
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();
@@ -469,7 +504,7 @@ internal sealed class PluginService : IPluginService
finally
{
// 释放锁资源
_locker.Release();
SaveLock.Release();
}
}
@@ -583,7 +618,7 @@ internal sealed class PluginService : IPluginService
if (_assemblyLoadContextDict.TryGetValue(path, out var assemblyLoadContext))
{
//移除字典
_driverBaseDict.RemoveWhere(a => path == PluginServiceUtil.GetFileNameAndTypeName(a.Key).FileName);
_driverBaseDict.RemoveWhere(a => path == PluginInfoUtil.GetFileNameAndTypeName(a.Key).FileName);
_assemblyLoadContextDict.Remove(path);
//卸载
assemblyLoadContext.AssemblyLoadContext.Unload();
@@ -713,6 +748,7 @@ internal sealed class PluginService : IPluginService
{
var plugins = new List<PluginInfo>();
// 主程序上下文
string tempDir = DirName;
// 遍历程序集上下文默认驱动字典,生成默认驱动插件信息
foreach (var item in _defaultDriverBaseDict)
@@ -740,7 +776,7 @@ internal sealed class PluginService : IPluginService
}
// 获取插件文件夹路径列表
string[] folderPaths = Directory.GetDirectories(AppContext.BaseDirectory.CombinePathWithOs(DirName));
string[] folderPaths = Directory.GetDirectories(AppContext.BaseDirectory.CombinePathWithOs(tempDir));
// 遍历插件文件夹
foreach (string folderPath in folderPaths)
@@ -769,7 +805,7 @@ internal sealed class PluginService : IPluginService
{
//添加到字典
_driverBaseDict.TryAdd($"{driverMainName}.{type.Name}", type);
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, PluginServiceUtil.GetFullName(driverMainName, type.Name)));
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, PluginInfoUtil.GetFullName(driverMainName, type.Name)));
}
var plugin = new PluginInfo()
{
@@ -789,10 +825,15 @@ internal sealed class PluginService : IPluginService
catch (Exception ex)
{
// 记录加载插件失败的日志
_logger?.LogWarning(ex, string.Format(AppResource.LoadPluginFail, Path.GetRelativePath(AppContext.BaseDirectory.CombinePathWithOs(DirName), folderPath)));
_logger?.LogWarning(ex, string.Format(AppResource.LoadPluginFail, Path.GetRelativePath(AppContext.BaseDirectory.CombinePathWithOs(tempDir), folderPath)));
}
}
return plugins.DistinctBy(a => a.FullName).OrderBy(a => a.EducationPlugin);
}
}
//public Task SavePlugin(PluginAddInput plugin)
//{
// return PluginInfoUtil.SavePlugin(plugin);
//}
}

View File

@@ -67,44 +67,6 @@ public static class PluginServiceUtil
return cols;
}
/// <summary>
/// 根据插件FullName获取插件主程序集名称和插件类型名称
/// </summary>
/// <param name="pluginName"></param>
/// <returns></returns>
public static (string FileName, string TypeName) GetFileNameAndTypeName(string pluginName)
{
if (pluginName.IsNullOrWhiteSpace())
return (string.Empty, string.Empty);
// 查找最后一个 '.' 的索引
int lastIndex = pluginName.LastIndexOf('.');
// 如果找到了最后一个 '.',并且它不是最后一个字符
if (lastIndex != -1 && lastIndex < pluginName.Length - 1)
{
// 获取子串直到最后一个 '.'
string part1 = pluginName.Substring(0, lastIndex);
// 获取最后一个 '.' 后面的部分
string part2 = pluginName.Substring(lastIndex + 1);
return (part1, part2);
}
else
{
// 如果没有找到 '.',或者 '.' 是最后一个字符,则返回默认的键和插件名称
return (nameof(ThingsGateway), pluginName);
}
}
/// <summary>
/// 根据插件主程序集名称和插件类型名称获取插件FullName
/// </summary>
/// <param name="fileName"></param>
/// <param name="name"></param>
/// <returns></returns>
public static string GetFullName(string fileName, string name)
{
return string.IsNullOrEmpty(fileName) ? name : $"{fileName}.{name}";
}
public static bool HasDynamicProperty(object model)
{

View File

@@ -8,13 +8,11 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using ThingsGateway.Blazor.Diagrams.Core;
namespace ThingsGateway.Gateway.Application;
public interface IRulesEngineHostedService : IHostedService
public interface IRulesEngineHostedService
{
Dictionary<RulesLog, Diagram> Diagrams { get; }
Task Delete(IEnumerable<long> ids);

View File

@@ -97,7 +97,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
private (RulesLog rulesLog, DefaultDiagram blazorDiagram) Init(Rules rules)
{
#pragma warning disable CA1863
var log = TextFileLogger.GetMultipleFileLogger(string.Format(LogPathFormat, rules.Name));
var log = TextFileLogger.GetMultipleFileLogger(string.Format(LogPathFormat, rules.Name.SanitizeFileName()));
#pragma warning restore CA1863
log.LogLevel = TouchSocket.Core.LogLevel.Trace;
DefaultDiagram blazorDiagram = new();

View File

@@ -594,7 +594,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
// 设备页导入预览输出
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
var driverPluginNameDict = _pluginService.GetList().ToDictionary(a => a.Name);
var driverPluginNameDict = _pluginService.GetPluginListSync().ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
// 遍历每个工作表

View File

@@ -103,7 +103,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
foreach (var plugin in pluginDrivers.Keys.Distinct())
{
var filtered = FilterPluginDevices(data, plugin, deviceDicts, channelDicts);
var pluginName = PluginServiceUtil.GetFileNameAndTypeName(plugin).Item2;
var pluginName = PluginInfoUtil.GetFileNameAndTypeName(plugin).Item2;
sheets.Add(pluginName, GetPluginSheets(filtered, deviceDicts, channelDicts, plugin, pluginDrivers, propertysDict));
}
@@ -239,7 +239,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
foreach (var plugin in pluginDrivers.Keys.Distinct())
{
var filtered = FilterPluginDevices(data, plugin, deviceDicts, channelDicts);
var pluginName = PluginServiceUtil.GetFileNameAndTypeName(plugin).Item2;
var pluginName = PluginInfoUtil.GetFileNameAndTypeName(plugin).Item2;
sheets.Add(pluginName, GetPluginSheets(filtered, deviceDicts, channelDicts, plugin, pluginDrivers, propertysDict));
}
@@ -322,7 +322,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
}
var deviceDicts = GlobalData.IdDevices;
var channelDicts = GlobalData.IdChannels;
var driverPluginDicts = GlobalData.PluginService.GetList(PluginTypeEnum.Business).ToDictionary(a => a.FullName);
var driverPluginDicts = GlobalData.PluginService.GetPluginListSync(PluginTypeEnum.Business).ToDictionary(a => a.FullName);
//总数据
Dictionary<string, object> sheets = new();
//变量页
@@ -436,7 +436,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
if (!driverPluginDicts.ContainsKey(channel.PluginName))
continue;
var pluginName = PluginServiceUtil.GetFileNameAndTypeName(channel.PluginName);
var pluginName = PluginInfoUtil.GetFileNameAndTypeName(channel.PluginName);
//lock (devicePropertys)
{
if (devicePropertys.ContainsKey(pluginName.Item2))
@@ -510,10 +510,10 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
// 设备页导入预览输出
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
var plugins = GlobalData.PluginService.GetPluginListSync();
// 获取驱动插件的全名和名称的字典
var driverPluginFullNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.FullName);
var driverPluginNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.Name);
var driverPluginFullNameDict = plugins.ToDictionary(a => a.FullName);
var driverPluginNameDict = plugins.ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
var sheetNames = uSheetDatas.sheets.Keys.ToList();

View File

@@ -14,8 +14,6 @@ using Microsoft.Extensions.Logging;
using System.Reflection;
using ThingsGateway.Authentication;
using ThingsGateway.Management;
using ThingsGateway.Upgrade;
namespace ThingsGateway.Gateway.Application;
@@ -35,16 +33,15 @@ public class Startup : AppStartup
services.AddSingleton<IRedundancyService, RedundancyService>();
services.AddGatewayHostedService<IRedundancyHostedService, RedundancyHostedService>();
services.AddGatewayHostedService<IUpdateZipFileHostedService, UpdateZipFileHostedService>();
services.AddHostedService<RemoteManagementHostedService>();
services.AddHostedService<ManagementHostedService>();
services.AddHostedService<WebApiHostedService>();
services.AddSingleton<GatewayRedundantSerivce>();
services.AddSingleton<IGatewayRedundantSerivce>(provider => provider.GetRequiredService<GatewayRedundantSerivce>());
services.AddConfigurableOptions<UpgradeServerOptions>();
#endregion
services.AddSingleton<ITextFileReadService, TextFileReadService>();
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
@@ -81,9 +78,14 @@ public class Startup : AppStartup
services.AddSingleton<IDeviceService, DeviceService>();
services.AddSingleton<IDeviceRuntimeService, DeviceRuntimeService>();
services.AddSingleton<IPluginService, PluginService>();
services.AddSingleton<IPluginPageService>(a => a.GetService<IPluginService>());
services.AddSingleton<IBackendLogService, BackendLogService>();
services.AddSingleton<IRpcLogService, RpcLogService>();
services.AddSingleton<IRpcService, RpcService>();
services.AddSingleton<IAuthenticationService, AuthenticationService>();
services.AddSingleton<IRestartService, RestartService>();
services.AddSingleton<IChannelEnableService, ChannelEnableService>();
services.AddGatewayHostedService<IAlarmHostedService, AlarmHostedService>();
services.AddGatewayHostedService<IGatewayMonitorHostedService, GatewayMonitorHostedService>();

View File

@@ -50,18 +50,4 @@
<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams.Core\ThingsGateway.Blazor.Diagrams.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Extensions\LogExtensions.cs" Link="Services\Management\Update\Files\LogExtensions.cs" />
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Model\UpdateZipFile.cs" Link="Services\Management\Update\Files\UpdateZipFile.cs" />
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Model\UpdateZipFileInput.cs" Link="Services\Management\Update\Files\UpdateZipFileInput.cs" />
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Services\IFileRpcServer.cs" Link="Services\Management\Update\Files\IFileRpcServer.cs" />
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Services\FileConst.cs" Link="Services\Management\Update\Files\FileConst.cs" />
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Services\FilePlugin.cs" Link="Services\Management\Update\Files\FilePlugin.cs" />
<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Services\UpgradeServerOptions.cs" Link="Services\Management\Update\Files\UpgradeServerOptions.cs" />
</ItemGroup>
</Project>

View File

@@ -17,7 +17,7 @@
},
"ThingsGateway.Management.Authentication": {
"ThingsGateway.Gateway.Razor.Authentication": {
"AuthName": "AuthName",
"Authorized": "Authorized",
"ExpireTime": "ExpireTime",

View File

@@ -15,7 +15,7 @@
"ActualMax": "实际最大值"
},
"ThingsGateway.Management.Authentication": {
"ThingsGateway.Gateway.Razor.Authentication": {
"AuthName": "公司名称",
"Authorized": "已授权",
"ExpireTime": "过期时间",

View File

@@ -58,8 +58,10 @@ public partial class ChannelEditComponent
protected override void OnInitialized()
{
PluginNames = GlobalData.PluginService.GetList(PluginType).BuildPluginSelectList();
PluginDcit = GlobalData.PluginService.GetList(PluginType).ToDictionary(a => a.FullName);
var plugins = GlobalData.PluginService.GetPluginListSync(PluginType);
PluginDcit = plugins.ToDictionary(a => a.FullName);
PluginNames = plugins.BuildPluginSelectList();
base.OnInitialized();
}
protected override Task OnParametersSetAsync()

View File

@@ -56,7 +56,7 @@ public partial class GatewayMonitorPage
}
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
{
var pluginType = GlobalData.PluginService.GetList().FirstOrDefault(a => a.FullName == pluginName)?.PluginType;
var pluginType = GlobalData.PluginService.GetPluginListSync().FirstOrDefault(a => a.FullName == pluginName)?.PluginType;
if (pluginType == PluginTypeEnum.Collect)
{
VariableRuntimes = channels.Where(a => a.PluginName == pluginName).SelectMany(a => a.ReadDeviceRuntimes).SelectMany(a => a.Value.ReadOnlyVariableRuntimes).Select(a => a.Value).Where(a => a != null);

View File

@@ -237,7 +237,7 @@
GlobalData.ReadOnlyIdDevices.TryGetValue(item.Key, out var items);
}
<div class="flex-fill">
@($"{items.Name} - {PluginServiceUtil.GetFileNameAndTypeName(items?.PluginName).TypeName}")
@($"{items.Name} - {PluginInfoUtil.GetFileNameAndTypeName(items?.PluginName).TypeName}")
</div>
<Button OnClick=@((a)=>

View File

@@ -1,10 +1,17 @@
@namespace ThingsGateway.Gateway.Razor
@page "/gateway/backendlog"
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application
@{
#if !Management
}
@page "/gateway/backendlog"
@attribute [Authorize]
@attribute [RolePermission]
@{
#endif
}
@inherits ComponentDefault
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" />

View File

@@ -26,7 +26,7 @@ public partial class BackendLogPage
if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested) return (new ChartDataSource());
if (ChartDataSource == null)
{
var dayStatisticsOutputs = await BackendLogService.StatisticsByDayAsync(7);
var dayStatisticsOutputs = await BackendLogService.BackendLogStatisticsByDayAsync(7);
ChartDataSource = new ChartDataSource();
ChartDataSource.Options.Title = Localizer[nameof(BackendLog)];
ChartDataSource.Options.X.Title = Localizer["Date"];
@@ -59,7 +59,7 @@ public partial class BackendLogPage
}
else
{
var dayStatisticsOutputs = await BackendLogService.StatisticsByDayAsync(7);
var dayStatisticsOutputs = await BackendLogService.BackendLogStatisticsByDayAsync(7);
ChartDataSource.Labels = dayStatisticsOutputs.Select(a => a.Date);
ChartDataSource.Data[0].Data = dayStatisticsOutputs.Select(a => (object)a.DebugCount);
ChartDataSource.Data[1].Data = dayStatisticsOutputs.Select(a => (object)a.InfoCount);
@@ -79,7 +79,7 @@ public partial class BackendLogPage
{
return await Task.Run(async () =>
{
var data = await BackendLogService.PageAsync(options);
var data = await BackendLogService.BackendLogPageAsync(options);
return data;
});
}

View File

@@ -1,10 +1,19 @@
@namespace ThingsGateway.Gateway.Razor
@page "/gateway/rpclog"
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application
@{
#if !Management
}
@page "/gateway/rpclog"
@attribute [Authorize]
@attribute [RolePermission]
@{
#endif
}
@inherits ComponentDefault
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" />

View File

@@ -26,7 +26,7 @@ public partial class RpcLogPage
if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested) return (new ChartDataSource());
if (ChartDataSource == null)
{
var dayStatisticsOutputs = await RpcLogService.StatisticsByDayAsync(7);
var dayStatisticsOutputs = await RpcLogService.RpcLogStatisticsByDayAsync(7);
ChartDataSource = new ChartDataSource();
ChartDataSource.Options.Title = Localizer[nameof(RpcLog)];
ChartDataSource.Options.X.Title = Localizer["Date"];
@@ -47,7 +47,7 @@ public partial class RpcLogPage
}
else
{
var dayStatisticsOutputs = await RpcLogService.StatisticsByDayAsync(7);
var dayStatisticsOutputs = await RpcLogService.RpcLogStatisticsByDayAsync(7);
ChartDataSource.Labels = dayStatisticsOutputs.Select(a => a.Date);
ChartDataSource.Data[0].Data = dayStatisticsOutputs.Select(a => (object)a.SuccessCount);
ChartDataSource.Data[1].Data = dayStatisticsOutputs.Select(a => (object)a.FailCount);
@@ -65,7 +65,7 @@ public partial class RpcLogPage
{
return await Task.Run(async () =>
{
var data = await RpcLogService.PageAsync(options);
var data = await RpcLogService.RpcLogPageAsync(options);
return data;
});
}

View File

@@ -1,4 +1,4 @@
@namespace ThingsGateway.Management
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway
@using ThingsGateway.Authentication
@if (WebsiteOption.Value.ShowAuthorize)
@@ -14,8 +14,10 @@
@Localizer["UUID"]
</label>
<Textarea Value="@ProAuthentication.UUID" rows="5" />
@if (AuthorizeInfo != null)
{
<Textarea Value="@AuthorizeInfo.Uuid" rows="5" />
}
</div>
</div>

View File

@@ -13,7 +13,7 @@ using Microsoft.Extensions.Options;
using ThingsGateway.Authentication;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Razor;
/// <inheritdoc/>
public partial class Authentication
@@ -30,20 +30,21 @@ public partial class Authentication
private AuthorizeInfo AuthorizeInfo { get; set; }
[Inject]
ToastService ToastService { get; set; }
protected override void OnParametersSet()
[Inject]
IAuthenticationService AuthenticationService { get; set; }
protected override async Task OnParametersSetAsync()
{
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
var authorizeInfo = await AuthenticationService.TryGetAuthorizeInfo();
AuthorizeInfo = authorizeInfo;
base.OnParametersSet();
}
private async Task Register()
{
var result = ProAuthentication.TryAuthorize(Password, out var authorizeInfo);
if (result)
var result = await AuthenticationService.TryAuthorize(Password);
if (result.Auth)
{
AuthorizeInfo = authorizeInfo;
AuthorizeInfo = result;
await ToastService.Default();
}
else
@@ -54,8 +55,8 @@ public partial class Authentication
}
private async Task Unregister()
{
ProAuthentication.UnAuthorize();
_ = ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
await AuthenticationService.UnAuthorize();
var authorizeInfo = await AuthenticationService.TryGetAuthorizeInfo();
AuthorizeInfo = authorizeInfo;
await InvokeAsync(StateHasChanged);

View File

@@ -1,16 +1,14 @@
@using BootstrapBlazor.Components
@using Microsoft.AspNetCore.Authorization
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Upgrade
@using ThingsGateway.Debug
@using ThingsGateway.Gateway.Application
@using ThingsGateway.Admin.Application
@using ThingsGateway.Razor
@namespace ThingsGateway.Management
@namespace ThingsGateway.Gateway.Razor
<span class=@("text-h6 mx-1") style="overflow:auto;">
<span class="text-truncate">
@RedundancyLocalizer["Status"]@(GlobalData.StartCollectChannelEnable ? RedundancyLocalizer["Master"] : RedundancyLocalizer["Slave"])
@RedundancyLocalizer["Status"]@(StartCollectChannelEnable ? RedundancyLocalizer["Master"] : RedundancyLocalizer["Slave"])
</span>
</span>

View File

@@ -9,7 +9,7 @@
//------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Razor;
public partial class RedundancyOptionsHeader : IDisposable
{
@@ -22,14 +22,22 @@ public partial class RedundancyOptionsHeader : IDisposable
{
Disposed = true;
}
protected override Task OnInitializedAsync()
[Inject]
IChannelEnableService ChannelEnableService { get; set; }
private bool StartCollectChannelEnable;
protected override async Task OnInitializedAsync()
{
StartCollectChannelEnable = await ChannelEnableService.StartCollectChannelEnable();
_ = Task.Run(async () =>
{
while (!Disposed)
{
try
{
StartCollectChannelEnable = await ChannelEnableService.StartCollectChannelEnable();
await InvokeAsync(StateHasChanged);
}
finally
@@ -38,6 +46,6 @@ public partial class RedundancyOptionsHeader : IDisposable
}
}
});
return base.OnInitializedAsync();
await base.OnInitializedAsync();
}
}

View File

@@ -2,31 +2,34 @@
@using BootstrapBlazor.Components
@using Microsoft.AspNetCore.Authorization
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Debug
@using ThingsGateway.Gateway.Application
@using ThingsGateway.Admin.Application
@using ThingsGateway.Razor
@namespace ThingsGateway.Management
@namespace ThingsGateway.Gateway.Razor
@if(Model!=null)
{
<div class=@($"{ClassString} row g-0 h-100 mx-2") style="min-height:500px;">
<div class=@($"{ClassString} row g-0 h-100 mx-2") style="min-height:500px;">
<div class="col-12 col-md-6 h-100">
<div class="col-12 col-md-6 h-100">
<EditComponent ItemsPerRow=1 Model="Model" OnSave="OnSaveRedundancy" />
<EditComponent ItemsPerRow=1 Model="Model" OnSave="OnSaveRedundancy" />
<Button IsDisabled=@(!Model.IsMaster||(!Model.Enable)) OnClick="RedundancyForcedSync">@RedundancyLocalizer["RedundancyForcedSync"]</Button>
</div>
<div class="col-12 col-md-6 h-100">
<Button IsDisabled=@(!Model.IsMaster||(!Model.Enable)) OnClick="ForcedSync">@RedundancyLocalizer["ForcedSync"]</Button>
<ThingsGateway.Debug.LogConsole HeightString="calc(100% - 100px)" LogLevel=@(LogLevel) LogLevelChanged="async (a)=>{
LogLevel=a;
if(LogLevelChanged!=null)
{
await LogLevelChanged?.Invoke(a);
}
}" LogPath=@LogPath HeaderText=@HeaderText></ThingsGateway.Debug.LogConsole>
</div>
</div>
<div class="col-12 col-md-6 h-100">
@if (Logger != null)
{
<LogConsole HeightString="calc(100% - 100px)" LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
Logger.LogLevel=a;
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
}
</div>
</div>
}

View File

@@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Components.Web;
using TouchSocket.Core;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Razor;
public partial class RedundancyOptionsPage
{
@@ -34,17 +34,37 @@ public partial class RedundancyOptionsPage
private RedundancyOptions Model { get; set; }
[Parameter, EditorRequired]
public ILog Logger { get; set; }
public LogLevel LogLevel { get; set; }
[Parameter, EditorRequired]
public Func<LogLevel, Task> LogLevelChanged { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
var logLevel = await RedundancyHostedService.RedundancyLogLevel();
if (logLevel != LogLevel)
{
LogLevel = logLevel;
if (LogLevelChanged != null)
{
await LogLevelChanged?.Invoke(LogLevel);
}
await InvokeAsync(StateHasChanged);
}
await base.OnAfterRenderAsync(firstRender);
}
[Inject]
[NotNull]
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
/// <inheritdoc/>
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
HeaderText = GatewayLocalizer[nameof(HeaderText)];
Model = (await RedundancyService.GetRedundancyAsync()).AdaptRedundancyOptions();
await base.OnInitializedAsync();
}
[Inject]
@@ -55,6 +75,9 @@ public partial class RedundancyOptionsPage
[NotNull]
private IRedundancyService? RedundancyService { get; set; }
[Inject]
[NotNull]
private SwalService? SwalService { get; set; }
@@ -72,8 +95,8 @@ public partial class RedundancyOptionsPage
await RedundancyService.EditRedundancyOptionAsync(Model);
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Save"]}{RazorLocalizer["Success"]}");
await RedundancyHostedService.StopTaskAsync();
await RedundancyHostedService.StartTaskAsync(CancellationToken.None);
await RedundancyHostedService.StopRedundancyTaskAsync();
await RedundancyHostedService.StartRedundancyTaskAsync();
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Success"]}");
await InvokeAsync(StateHasChanged);
@@ -85,7 +108,7 @@ public partial class RedundancyOptionsPage
}
}
private async Task ForcedSync(MouseEventArgs args)
private async Task RedundancyForcedSync(MouseEventArgs args)
{
var ret = await SwalService.ShowModal(new SwalOption()
{
@@ -94,7 +117,7 @@ public partial class RedundancyOptionsPage
});
if (ret)
{
await RedundancyHostedService.ForcedSync();
await RedundancyHostedService.RedundancyForcedSync();
}
}
}

View File

@@ -1,17 +1,22 @@
@page "/gateway/system"
@{
#if !Management
}
@page "/gateway/system"
@attribute [Authorize]
@attribute [RolePermission]
@{
#endif
}
@inherits ComponentDefault
@using BootstrapBlazor.Components
@using Microsoft.AspNetCore.Authorization
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Authentication
@using ThingsGateway.Upgrade
@using ThingsGateway.Debug
@using ThingsGateway.Gateway.Application
@using ThingsGateway.Admin.Application
@using ThingsGateway.Razor
@namespace ThingsGateway.Management
@namespace ThingsGateway.Gateway.Razor
<div class="appconfig">
@@ -29,7 +34,7 @@
<BodyTemplate>
<RedundancyOptionsPage Logger="@RedundancyHostedService.TextLogger" LogPath="@RedundancyHostedService.LogPath" />
<RedundancyOptionsPage LogLevel="LogLevel" LogLevelChanged=@(SetLogLevel) LogPath="@LogPath" />
</BodyTemplate>
@@ -37,19 +42,6 @@
</TabItem>
<TabItem Text=@GatewayLocalizer["CheckUpdate"]>
<Card class="h-100">
<BodyTemplate>
<UpdateZipFilePage></UpdateZipFilePage>
</BodyTemplate>
</Card>
</TabItem>
<TabItem Text=@GatewayLocalizer["Restart"]>
<Card class="h-100">

View File

@@ -11,7 +11,7 @@
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using Microsoft.Extensions.Options;
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Razor;
public partial class SystemConfigPage
{
@@ -27,11 +27,23 @@ public partial class SystemConfigPage
[NotNull]
private SwalService? SwalService { get; set; }
private string LogPath;
private TouchSocket.Core.LogLevel LogLevel;
private Task SetLogLevel(TouchSocket.Core.LogLevel logLevel)
{
LogLevel = logLevel;
return RedundancyHostedService.SetRedundancyLogLevel(logLevel);
}
protected override async Task OnInitializedAsync()
{
LogPath = await RedundancyHostedService.RedundancyLogPath();
LogLevel = await RedundancyHostedService.RedundancyLogLevel();
await base.OnInitializedAsync();
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
TabItem?.SetHeader(AppContext.TitleLocalizer["系统管理"]);
if (firstRender && Tab != null && tabComponent != null)
{
tabComponent.ActiveTab(Tab.Value);
@@ -49,6 +61,8 @@ public partial class SystemConfigPage
[NotNull]
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
[Inject]
public IRestartService RestartService { get; set; }
private async Task OnRestart()
{
var result = await SwalService.ShowModal(new SwalOption()
@@ -58,7 +72,7 @@ public partial class SystemConfigPage
});
if (result)
{
RestartServerHelper.RestartServer();
await RestartService.RestartServer();
}
}

View File

@@ -1,49 +0,0 @@
@using Microsoft.AspNetCore.Components.Web;
@using Microsoft.JSInterop;
@using ThingsGateway.Debug
@using ThingsGateway.Extension
@using ThingsGateway.Foundation
@using BootstrapBlazor.Components
@namespace ThingsGateway.Upgrade
<div class=@($"{ClassString} row my-2 mx-2") style="min-height:500px;height: 50%;">
<div class="col-3 h-100">
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-8 p-1">
<Button IsAsync Color="Color.Primary" OnClick="OnUpdate">@GatewayLocalizer["Update"]</Button>
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="OnRestart">
@GatewayLocalizer["Restart"]
</Button>
</div>
</div>
<Divider />
</div>
<div class="col-9 ">
@if (Logger != null)
{
<LogConsole HeightString="500px" LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
Logger.LogLevel=a;
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
}
</div>
</div>

View File

@@ -1,82 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using ThingsGateway.Management;
using TouchSocket.Core;
namespace ThingsGateway.Upgrade;
/// <inheritdoc/>
public partial class UpdateZipFileInfo
{
[Parameter]
public string ClassString { get; set; }
[Parameter]
public string HeaderText { get; set; }
[Parameter, EditorRequired]
public string LogPath { get; set; }
[Parameter, EditorRequired]
public UpdateZipFile Model { get; set; }
[Parameter, EditorRequired]
public ILog Logger { get; set; }
[Inject]
private ToastService ToastService { get; set; }
[Inject]
private IUpdateZipFileHostedService UpdateZipFileHostedService { get; set; }
[Inject]
[NotNull]
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
/// <inheritdoc/>
protected override void OnInitialized()
{
base.OnInitialized();
HeaderText = GatewayLocalizer[nameof(HeaderText)];
}
[Inject]
private SwalService SwalService { get; set; }
private async Task OnRestart()
{
var result = await SwalService.ShowModal(new SwalOption()
{
Category = SwalCategory.Warning,
Title = GatewayLocalizer["Restart"]
});
if (result)
{
RestartServerHelper.RestartServer();
}
}
private async Task OnUpdate()
{
await Task.Run(async () =>
{
try
{
await UpdateZipFileHostedService.Update(Model, async () =>
{
var result = await SwalService.ShowModal(new SwalOption()
{
Category = SwalCategory.Warning,
Title = GatewayLocalizer["Restart"]
});
return result;
});
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
});
}
}

View File

@@ -1,39 +0,0 @@
@using BootstrapBlazor.Components
@using ThingsGateway.Razor
@namespace ThingsGateway.Upgrade
<div class="h-100">
<DefaultTable TItem="UpdateZipFile"
AutoGenerateColumns="true"
ShowDefaultButtons=false
EditDialogSize="Size.Large"
AllowResizing="true"
IsFixedHeader=true
ShowAddButton="false"
ShowRefresh="true"
ShowEmpty="true"
ShowSearch="false"
IsMultipleSelect=false
ShowExtendEditButton=false
ShowExtendDeleteButton=false
ShowExtendButtons=true
ShowDeleteButton="false"
ShowEditButton="false"
ShowAdvancedSearch=false
OnQueryAsync="OnQueryAsync">
@* <TableToolbarTemplate>
<TableToolbarButton TItem="UpdateZipFile" Color="Color.Info" Icon="fa fa-plus" Text="test"
OnClickCallback="a=>ShowInfo(a.FirstOrDefault())" />
</TableToolbarTemplate> *@
<RowButtonTemplate>
<TableCellButton Size="Size.ExtraSmall" Color="Color.Success" Icon="fa-solid fa-people-roof" Text="@GatewayLocalizer["Info"]" OnClick="() => ShowInfo(context)" />
</RowButtonTemplate>
</DefaultTable>
</div>

View File

@@ -1,67 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using ThingsGateway.Management;
namespace ThingsGateway.Upgrade;
public partial class UpdateZipFilePage
{
[Inject]
[NotNull]
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
#region
[Inject]
private ToastService ToastService { get; set; }
[Inject]
private DialogService DialogService { get; set; }
[Inject]
[NotNull]
private IUpdateZipFileHostedService? UpdateZipFileHostedService { get; set; }
private async Task<QueryData<UpdateZipFile>> OnQueryAsync(QueryPageOptions options)
{
try
{
var data = await UpdateZipFileHostedService.GetList();
return new QueryData<UpdateZipFile>() { Items = data };
}
catch
{
return new QueryData<UpdateZipFile>();
}
}
#endregion
private async Task ShowInfo(UpdateZipFile updateZipFile)
{
var op = new DialogOption()
{
IsScrolling = true,
Size = Size.ExtraExtraLarge,
Title = GatewayLocalizer["Info"],
ShowCloseButton = false,
ShowMaximizeButton = true,
ShowSaveButton = false,
Class = "dialog-table",
BodyTemplate = BootstrapDynamicComponent.CreateComponent<UpdateZipFileInfo>(new Dictionary<string, object?>
{
[nameof(UpdateZipFileInfo.Logger)] = UpdateZipFileHostedService.TextLogger,
[nameof(UpdateZipFileInfo.LogPath)] = UpdateZipFileHostedService.LogPath,
[nameof(UpdateZipFileInfo.Model)] = updateZipFile,
}).Render(),
};
await DialogService.Show(op);
}
}

View File

@@ -26,7 +26,7 @@ public partial class PluginDebugPage
/// <inheritdoc/>
protected override void OnParametersSet()
{
var pluginInfos = PluginService.GetList().AdaptListPluginInfo();
var pluginInfos = PluginService.GetPluginListSync().AdaptListPluginInfo();
foreach (var pluginInfo in pluginInfos.ToList())
{

View File

@@ -1,7 +1,14 @@
@page "/gateway/plugin"

@{
#if !Management
}
@page "/gateway/plugin"
@attribute [Authorize]
@attribute [RolePermission]
@{
#endif
}
@namespace ThingsGateway.Gateway.Razor
@using BootstrapBlazor.Components
@using ThingsGateway.Admin.Application

View File

@@ -16,7 +16,7 @@ public partial class PluginPage
[Inject]
[NotNull]
private IPluginService? PluginService { get; set; }
private IPluginPageService? PluginService { get; set; }
private PluginInfo SearchModel { get; set; } = new();
@@ -24,7 +24,7 @@ public partial class PluginPage
{
return await Task.Run(() =>
{
var data = PluginService.Page(options);
var data = PluginService.PluginPage(options);
return data;
});
}
@@ -69,14 +69,14 @@ public partial class PluginPage
};
op.Component = BootstrapDynamicComponent.CreateComponent<SavePlugin>(new Dictionary<string, object?>
{
[nameof(SavePlugin.OnSavePlugin)] = new Func<PluginAddInput, Task>(PluginService.SavePlugin),
[nameof(SavePlugin.OnSavePlugin)] = new Func<PluginAddInput, Task>(PluginInfoUtil.SavePlugin),
});
await DialogService.Show(op);
}
private void OnReload()
private Task OnReload()
{
PluginService.Reload();
return PluginService.ReloadPlugin();
}
#endregion

Some files were not shown because too many files have changed in this diff Show More