mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-22 11:33:07 +08:00
!69 更新依赖
This commit is contained in:
@@ -27,16 +27,17 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">数据类型</typeparam>
|
/// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="insertDatas">要插入的数据列表</param>
|
/// <param name="insertDatas">要插入的数据列表</param>
|
||||||
|
/// <param name="tableName">表名称</param>
|
||||||
/// <param name="dateFormat">日期格式字符串</param>
|
/// <param name="dateFormat">日期格式字符串</param>
|
||||||
/// <returns>插入的记录数</returns>
|
/// <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;
|
int result = 0;
|
||||||
// 使用分页方式处理大数据量插入
|
// 使用分页方式处理大数据量插入
|
||||||
db.Utilities.PageEach(insertDatas, pageSize, pageItems =>
|
db.Utilities.PageEach(insertDatas, pageSize, pageItems =>
|
||||||
{
|
{
|
||||||
// 同步调用批量插入API并累加结果
|
// 同步调用批量插入API并累加结果
|
||||||
result += questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).GetAwaiter().GetResult();
|
result += questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).GetAwaiter().GetResult();
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -46,16 +47,17 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">数据类型</typeparam>
|
/// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="insertDatas">要插入的数据列表</param>
|
/// <param name="insertDatas">要插入的数据列表</param>
|
||||||
|
/// <param name="tableName">表名称</param>
|
||||||
/// <param name="dateFormat">日期格式字符串</param>
|
/// <param name="dateFormat">日期格式字符串</param>
|
||||||
/// <returns>插入的记录数</returns>
|
/// <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;
|
int result = 0;
|
||||||
// 异步分页处理大数据量插入
|
// 异步分页处理大数据量插入
|
||||||
await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems =>
|
await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems =>
|
||||||
{
|
{
|
||||||
// 异步调用批量插入API并累加结果
|
// 异步调用批量插入API并累加结果
|
||||||
result += await questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).ConfigureAwait(false);
|
result += await questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).ConfigureAwait(false);
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@@ -7,16 +7,12 @@ namespace ThingsGateway.SqlSugar
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 绑定RestAPI需要的信息
|
/// 绑定RestAPI需要的信息
|
||||||
/// </summary>
|
/// </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))
|
if (builder.TryGetValue("Host", out object hostValue))
|
||||||
{
|
{
|
||||||
host = Convert.ToString(hostValue);
|
host = Convert.ToString(hostValue);
|
||||||
}
|
}
|
||||||
if (builder.TryGetValue("HttpPort", out object httpPortValue))
|
|
||||||
{
|
|
||||||
httpPort = Convert.ToString(httpPortValue);
|
|
||||||
}
|
|
||||||
if (builder.TryGetValue("Username", out object usernameValue))
|
if (builder.TryGetValue("Username", out object usernameValue))
|
||||||
{
|
{
|
||||||
username = Convert.ToString(usernameValue);
|
username = Convert.ToString(usernameValue);
|
||||||
|
@@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar
|
|||||||
/// 初始化 QuestDbRestAPI 实例
|
/// 初始化 QuestDbRestAPI 实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="db">SqlSugar 数据库客户端</param>
|
/// <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();
|
var builder = new DbConnectionStringBuilder();
|
||||||
builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
|
builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
string httpPort = String.Empty;
|
|
||||||
string host = String.Empty;
|
string host = String.Empty;
|
||||||
string username = String.Empty;
|
string username = String.Empty;
|
||||||
string password = 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);
|
BindHost(host, httpPort, username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,34 +68,34 @@ namespace ThingsGateway.SqlSugar
|
|||||||
return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
|
return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// 异步批量插入单条数据
|
///// 异步批量插入单条数据
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <typeparam name="T">数据类型</typeparam>
|
///// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="insertData">要插入的数据</param>
|
///// <param name="insertData">要插入的数据</param>
|
||||||
/// <param name="dateFormat">日期格式字符串</param>
|
///// <param name="dateFormat">日期格式字符串</param>
|
||||||
/// <returns>影响的行数</returns>
|
///// <returns>影响的行数</returns>
|
||||||
public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
//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)
|
// if (db.CurrentConnectionConfig.MoreSettings == null)
|
||||||
db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
|
// db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
|
||||||
db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
|
// db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
|
||||||
var sql = db.InsertableT(insertData).ToSqlString();
|
// var sql = db.InsertableT(insertData).ToSqlString();
|
||||||
var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
|
// var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
|
||||||
return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
// return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// 同步批量插入单条数据
|
///// 同步批量插入单条数据
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <typeparam name="T">数据类型</typeparam>
|
///// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="insertData">要插入的数据</param>
|
///// <param name="insertData">要插入的数据</param>
|
||||||
/// <param name="dateFormat">日期格式字符串</param>
|
///// <param name="dateFormat">日期格式字符串</param>
|
||||||
/// <returns>影响的行数</returns>
|
///// <returns>影响的行数</returns>
|
||||||
public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
//public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||||
{
|
//{
|
||||||
return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
|
// return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建分页批量插入器
|
/// 创建分页批量插入器
|
||||||
@@ -115,9 +115,10 @@ namespace ThingsGateway.SqlSugar
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">数据类型</typeparam>
|
/// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="insertList">要插入的数据列表</param>
|
/// <param name="insertList">要插入的数据列表</param>
|
||||||
|
/// <param name="tableName">表名称</param>
|
||||||
/// <param name="dateFormat">日期格式字符串</param>
|
/// <param name="dateFormat">日期格式字符串</param>
|
||||||
/// <returns>插入的记录数</returns>
|
/// <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 result = 0;
|
||||||
var fileName = $"{Guid.NewGuid()}.csv";
|
var fileName = $"{Guid.NewGuid()}.csv";
|
||||||
@@ -127,12 +128,12 @@ namespace ThingsGateway.SqlSugar
|
|||||||
// 准备多部分表单数据
|
// 准备多部分表单数据
|
||||||
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
|
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
|
||||||
var list = new List<Hashtable>();
|
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 key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
|
||||||
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
|
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
|
||||||
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name));
|
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
|
||||||
|
|
||||||
// 构建schema信息
|
// 构建schema信息
|
||||||
columns.ForEach(d =>
|
columns.ForEach(d =>
|
||||||
@@ -170,8 +171,8 @@ namespace ThingsGateway.SqlSugar
|
|||||||
// 准备HTTP请求内容
|
// 准备HTTP请求内容
|
||||||
using var httpContent = new MultipartFormDataContent(boundary);
|
using var httpContent = new MultipartFormDataContent(boundary);
|
||||||
using var fileStream = File.OpenRead(filePath);
|
using var fileStream = File.OpenRead(filePath);
|
||||||
if (!string.IsNullOrWhiteSpace(this.authorization))
|
//if (!string.IsNullOrWhiteSpace(this.authorization))
|
||||||
client.DefaultRequestHeaders.Add("Authorization", this.authorization);
|
// client.DefaultRequestHeaders.Add("Authorization", this.authorization);
|
||||||
httpContent.Add(new StringContent(schema), "schema");
|
httpContent.Add(new StringContent(schema), "schema");
|
||||||
var streamContent = new StreamContent(fileStream);
|
var streamContent = new StreamContent(fileStream);
|
||||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||||
@@ -184,7 +185,7 @@ namespace ThingsGateway.SqlSugar
|
|||||||
|
|
||||||
// 发送请求并处理响应
|
// 发送请求并处理响应
|
||||||
var httpResponseMessage =
|
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 readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
|
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
|
||||||
|
|
||||||
@@ -266,11 +267,12 @@ namespace ThingsGateway.SqlSugar
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">数据类型</typeparam>
|
/// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="insertList">要插入的数据列表</param>
|
/// <param name="insertList">要插入的数据列表</param>
|
||||||
|
/// <param name="tableName">表名称</param>
|
||||||
/// <param name="dateFormat">日期格式字符串</param>
|
/// <param name="dateFormat">日期格式字符串</param>
|
||||||
/// <returns>插入的记录数</returns>
|
/// <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>
|
/// <summary>
|
||||||
@@ -280,7 +282,7 @@ namespace ThingsGateway.SqlSugar
|
|||||||
/// <param name="httpPort">HTTP端口</param>
|
/// <param name="httpPort">HTTP端口</param>
|
||||||
/// <param name="username">用户名</param>
|
/// <param name="username">用户名</param>
|
||||||
/// <param name="password">密码</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;
|
url = host;
|
||||||
if (url.EndsWith('/'))
|
if (url.EndsWith('/'))
|
||||||
|
@@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
public static class QuestDbSqlSugarClientExtensions
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -77,7 +77,8 @@ public partial class LogConsole : IDisposable
|
|||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ToastService ToastService { get; set; }
|
private ToastService ToastService { get; set; }
|
||||||
|
[Inject]
|
||||||
|
ITextFileReadService TextFileReadService { get; set; }
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
@@ -94,7 +95,7 @@ public partial class LogConsole : IDisposable
|
|||||||
|
|
||||||
if (LogPath != null)
|
if (LogPath != null)
|
||||||
{
|
{
|
||||||
var files = TextFileReader.GetFiles(LogPath);
|
var files = await TextFileReadService.GetLogFiles(LogPath);
|
||||||
if (!files.IsSuccess)
|
if (!files.IsSuccess)
|
||||||
{
|
{
|
||||||
Messages = new List<LogMessage>();
|
Messages = new List<LogMessage>();
|
||||||
@@ -105,7 +106,7 @@ public partial class LogConsole : IDisposable
|
|||||||
await Task.Run(async () =>
|
await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
var result = TextFileReader.LastLog(files.Content.FirstOrDefault());
|
var result = await TextFileReadService.LastLogData(files.Content.FirstOrDefault());
|
||||||
if (result.IsSuccess)
|
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();
|
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)
|
if (LogPath != null)
|
||||||
{
|
{
|
||||||
var files = TextFileReader.GetFiles(LogPath);
|
var files = await TextFileReadService.GetLogFiles(LogPath);
|
||||||
if (files.IsSuccess)
|
if (files.IsSuccess)
|
||||||
{
|
{
|
||||||
foreach (var item in files.Content)
|
foreach (var item in files.Content)
|
||||||
|
@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
|
|||||||
|
|
||||||
public async Task OnLogExport(string logPath)
|
public async Task OnLogExport(string logPath)
|
||||||
{
|
{
|
||||||
var files = TextFileReader.GetFiles(logPath);
|
var files = TextFileReader.GetLogFiles(logPath);
|
||||||
if (!files.IsSuccess)
|
if (!files.IsSuccess)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@@ -33,5 +33,6 @@ public class Startup : AppStartup
|
|||||||
}
|
}
|
||||||
|
|
||||||
services.AddScoped<IPlatformService, PlatformService>();
|
services.AddScoped<IPlatformService, PlatformService>();
|
||||||
|
services.AddSingleton<ITextFileReadService, TextFileReadService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,7 +59,7 @@ public static class TextFileReader
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="directoryPath">目录路径</param>
|
/// <param name="directoryPath">目录路径</param>
|
||||||
/// <returns>包含文件信息的列表</returns>
|
/// <returns>包含文件信息的列表</returns>
|
||||||
public static OperResult<List<string>> GetFiles(string directoryPath)
|
public static OperResult<List<string>> GetLogFiles(string directoryPath)
|
||||||
{
|
{
|
||||||
OperResult<List<string>> result = new(); // 初始化结果对象
|
OperResult<List<string>> result = new(); // 初始化结果对象
|
||||||
// 检查目录是否存在
|
// 检查目录是否存在
|
||||||
@@ -91,7 +91,7 @@ public static class TextFileReader
|
|||||||
return result;
|
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))
|
if (!File.Exists(file))
|
||||||
return new OperResult<List<LogData>>("The file path is invalid");
|
return new OperResult<List<LogData>>("The file path is invalid");
|
||||||
@@ -104,7 +104,7 @@ public static class TextFileReader
|
|||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(file);
|
var fileInfo = new FileInfo(file);
|
||||||
var length = fileInfo.Length;
|
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 (_cache.TryGetValue<LogDataCache>(cacheKey, out var cachedData))
|
||||||
{
|
{
|
||||||
if (cachedData != null && cachedData.Length == length)
|
if (cachedData != null && cachedData.Length == length)
|
||||||
|
@@ -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);
|
||||||
|
}
|
@@ -8,13 +8,13 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Riok.Mapperly.Abstractions;
|
|
||||||
|
|
||||||
using TouchSocket.Dmtp;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Upgrade;
|
namespace ThingsGateway.Foundation;
|
||||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
|
|
||||||
public static partial class UpgradeMapper
|
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));
|
||||||
}
|
}
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -145,7 +145,7 @@ public class RuntimeInfoController : ControllerBase, IRpcServer
|
|||||||
public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery][TouchSocket.WebApi.FromBody] PluginInfoPageInput input)
|
public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery][TouchSocket.WebApi.FromBody] PluginInfoPageInput input)
|
||||||
{
|
{
|
||||||
//指定关键词搜索为插件FullName
|
//指定关键词搜索为插件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);
|
.ToPagedList(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -19,30 +18,19 @@ namespace Microsoft.Extensions.DependencyInjection;
|
|||||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||||
public static class ServiceCollectionHostedServiceExtensions
|
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>
|
/// <summary>
|
||||||
/// Add an <see cref="IHostedService"/> registration for the given type.
|
/// Add an <see cref="IHostedService"/> registration for the given type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection AddGatewayHostedService<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
|
public static IServiceCollection AddGatewayHostedService<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
|
||||||
where TService : class, IHostedService
|
where TService : class
|
||||||
where THostedService : class, IHostedService, TService
|
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;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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": {
|
"ThingsGateway.Gateway.Application.DefaultDiagram": {
|
||||||
|
|
||||||
@@ -53,11 +71,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
"ThingsGateway.Management.AutoUpdateController": {
|
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
|
||||||
"AutoUpdateController": "AutoUpdate",
|
|
||||||
"Update": "Update"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Management.RedundancyHostedService": {
|
|
||||||
"ErrorSynchronizingData": "Synchronize data to standby site error",
|
"ErrorSynchronizingData": "Synchronize data to standby site error",
|
||||||
"RedundancyDisable": "Redundant gateway site not enabled",
|
"RedundancyDisable": "Redundant gateway site not enabled",
|
||||||
"RedundancyDup": "Redundant station settings duplicated",
|
"RedundancyDup": "Redundant station settings duplicated",
|
||||||
@@ -67,10 +81,10 @@
|
|||||||
"SwitchNormalState": "Local machine (primary site) will switch to normal state",
|
"SwitchNormalState": "Local machine (primary site) will switch to normal state",
|
||||||
"SwitchSlaveState": "Master site has recovered, local machine (standby) will switch to standby 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",
|
"Confirm": "Confirm switching to redundant state",
|
||||||
"Enable": "Enable Dual-Machine Redundancy",
|
"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?",
|
"ForcedSyncWarning": "Forcing synchronization will generate database configuration information.Are you sure you want to continue?",
|
||||||
"HeartbeatInterval": "Heartbeat Interval",
|
"HeartbeatInterval": "Heartbeat Interval",
|
||||||
"IsMaster": "IsMaster",
|
"IsMaster": "IsMaster",
|
||||||
@@ -86,13 +100,13 @@
|
|||||||
"SyncInterval": "Data Synchronization Interval",
|
"SyncInterval": "Data Synchronization Interval",
|
||||||
"VerifyToken": "Verification Token"
|
"VerifyToken": "Verification Token"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Management.RedundancyService": {
|
"ThingsGateway.Gateway.Application.RedundancyService": {
|
||||||
"EditRedundancyOption": "EditRedundancyOption"
|
"EditRedundancyOption": "EditRedundancyOption"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Management.UpdateZipFileHostedService": {
|
"ThingsGateway.Gateway.Application.UpdateZipFileService": {
|
||||||
"Update": "New version detected"
|
"Update": "New version detected"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Upgrade.UpdateZipFile": {
|
"ThingsGateway.Gateway.Application.UpdateZipFile": {
|
||||||
"AppName": "AppName",
|
"AppName": "AppName",
|
||||||
"Architecture": "Architecture",
|
"Architecture": "Architecture",
|
||||||
"DotNetVersion": "DotNetVersion",
|
"DotNetVersion": "DotNetVersion",
|
||||||
|
@@ -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": {
|
"ThingsGateway.Gateway.Application.DefaultDiagram": {
|
||||||
|
|
||||||
@@ -51,11 +69,8 @@
|
|||||||
"RulesId": "名称"
|
"RulesId": "名称"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Management.AutoUpdateController": {
|
|
||||||
"AutoUpdateController": "程序更新",
|
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
|
||||||
"Update": "更新"
|
|
||||||
},
|
|
||||||
"ThingsGateway.Management.RedundancyHostedService": {
|
|
||||||
"ErrorSynchronizingData": "同步数据到从站错误",
|
"ErrorSynchronizingData": "同步数据到从站错误",
|
||||||
"RedundancyDisable": "不启用网关冗余站点",
|
"RedundancyDisable": "不启用网关冗余站点",
|
||||||
"RedundancyDup": "主备站设置重复",
|
"RedundancyDup": "主备站设置重复",
|
||||||
@@ -65,10 +80,10 @@
|
|||||||
"SwitchNormalState": "本机(主站)将切换到正常状态",
|
"SwitchNormalState": "本机(主站)将切换到正常状态",
|
||||||
"SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态"
|
"SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Management.RedundancyOptions": {
|
"ThingsGateway.Gateway.Application.RedundancyOptions": {
|
||||||
"Confirm": "确认切换冗余状态",
|
"Confirm": "确认切换冗余状态",
|
||||||
"Enable": "启用双机冗余",
|
"Enable": "启用双机冗余",
|
||||||
"ForcedSync": "强制同步",
|
"RedundancyForcedSync": "强制同步",
|
||||||
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?",
|
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?",
|
||||||
"HeartbeatInterval": "心跳间隔",
|
"HeartbeatInterval": "心跳间隔",
|
||||||
"IsMaster": "是否为主站",
|
"IsMaster": "是否为主站",
|
||||||
@@ -84,13 +99,13 @@
|
|||||||
"SyncInterval": "数据同步间隔",
|
"SyncInterval": "数据同步间隔",
|
||||||
"VerifyToken": "Token"
|
"VerifyToken": "Token"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Management.RedundancyService": {
|
"ThingsGateway.Gateway.Application.RedundancyService": {
|
||||||
"EditRedundancyOption": "修改网关冗余配置"
|
"EditRedundancyOption": "修改网关冗余配置"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Management.UpdateZipFileHostedService": {
|
"ThingsGateway.Gateway.Application.UpdateZipFileService": {
|
||||||
"Update": "检测到新版本"
|
"Update": "检测到新版本"
|
||||||
},
|
},
|
||||||
"ThingsGateway.Upgrade.UpdateZipFile": {
|
"ThingsGateway.Gateway.Application.UpdateZipFile": {
|
||||||
"AppName": "名称",
|
"AppName": "名称",
|
||||||
"Architecture": "架构",
|
"Architecture": "架构",
|
||||||
"DotNetVersion": ".net版本",
|
"DotNetVersion": ".net版本",
|
||||||
|
@@ -10,8 +10,6 @@
|
|||||||
|
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
using ThingsGateway.Management;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
|
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
|
||||||
public static partial class GatewayMapper
|
public static partial class GatewayMapper
|
||||||
|
@@ -117,7 +117,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
|||||||
public void Init()
|
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.IdChannels.TryRemove(Id, out _);
|
||||||
GlobalData.Channels.TryRemove(Name, out _);
|
GlobalData.Channels.TryRemove(Name, out _);
|
||||||
|
@@ -27,7 +27,7 @@ public class PluginInfo
|
|||||||
/// 插件文件名称.插件类型名称
|
/// 插件文件名称.插件类型名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AutoGenerateColumn(Ignore = true)]
|
[AutoGenerateColumn(Ignore = true)]
|
||||||
public string FullName => PluginServiceUtil.GetFullName(FileName, Name);
|
public string FullName => PluginInfoUtil.GetFullName(FileName, Name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 插件文件名称
|
/// 插件文件名称
|
||||||
@@ -70,8 +70,5 @@ public class PluginInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[IgnoreExcel]
|
[IgnoreExcel]
|
||||||
[SugarColumn(IsIgnore = true)]
|
[SugarColumn(IsIgnore = true)]
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
[Newtonsoft.Json.JsonIgnore]
|
|
||||||
[AutoGenerateColumn(Ignore = true)]
|
|
||||||
public string Directory { get; set; }
|
public string Directory { get; set; }
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 系统配置种子数据
|
/// 系统配置种子数据
|
||||||
|
@@ -8,11 +8,9 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
public interface IAlarmHostedService : IHostedService
|
public interface IAlarmHostedService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 确认报警
|
/// 确认报警
|
||||||
|
@@ -260,7 +260,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
|||||||
HashSet<long>? channel = null;
|
HashSet<long>? channel = null;
|
||||||
if (exportFilter.PluginType != 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();
|
channel = GlobalData.IdChannels.Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
|
||||||
}
|
}
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
@@ -216,7 +216,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
|
|||||||
}
|
}
|
||||||
if (exportFilter.PluginType != 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();
|
channel = (GlobalData.IdChannels).Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
|
||||||
}
|
}
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
@@ -238,7 +238,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
|
|||||||
}
|
}
|
||||||
if (exportFilter.PluginType != 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();
|
channel = (GlobalData.IdChannels).Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
|
||||||
}
|
}
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
@@ -417,7 +417,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
|
|||||||
ImportPreviewOutput<Device> deviceImportPreview = new();
|
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();
|
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
||||||
foreach (var sheetName in sheetNames)
|
foreach (var sheetName in sheetNames)
|
||||||
{
|
{
|
||||||
|
@@ -50,7 +50,7 @@ HashSet<string> pluginSheetNames,
|
|||||||
foreach (var plugin in pluginSheetNames)
|
foreach (var plugin in pluginSheetNames)
|
||||||
{
|
{
|
||||||
var filtered = FilterPluginDevices(data, plugin, channelDicts);
|
var filtered = FilterPluginDevices(data, plugin, channelDicts);
|
||||||
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
|
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
|
||||||
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
|
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
|
||||||
result.Add(filtResult.TypeName, pluginSheets);
|
result.Add(filtResult.TypeName, pluginSheets);
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ string? channelName = null)
|
|||||||
foreach (var plugin in pluginSheetNames)
|
foreach (var plugin in pluginSheetNames)
|
||||||
{
|
{
|
||||||
var filtered = FilterPluginDevices(data2, plugin, channelDicts);
|
var filtered = FilterPluginDevices(data2, plugin, channelDicts);
|
||||||
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
|
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
|
||||||
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
|
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
|
||||||
result.Add(filtResult.TypeName, pluginSheets);
|
result.Add(filtResult.TypeName, pluginSheets);
|
||||||
}
|
}
|
||||||
@@ -288,10 +288,10 @@ string? channelName)
|
|||||||
ImportPreviewOutput<Device> deviceImportPreview = new();
|
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();
|
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
||||||
|
|
||||||
var sheetNames = uSheetDatas.sheets.Keys.ToList();
|
var sheetNames = uSheetDatas.sheets.Keys.ToList();
|
||||||
|
@@ -21,7 +21,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最新十条
|
/// 最新十条
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<List<BackendLog>> GetNewLog()
|
public async Task<List<BackendLog>> GetNewBackendLog()
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
var data = await db.Queryable<BackendLog>().OrderByDescending(a => a.LogTime).Take(10).ToListAsync().ConfigureAwait(false);
|
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>
|
/// </summary>
|
||||||
/// <param name="option">查询条件</param>
|
/// <param name="option">查询条件</param>
|
||||||
public Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option)
|
public Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option)
|
||||||
{
|
{
|
||||||
return QueryAsync(option);
|
return QueryAsync(option);
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="day">天</param>
|
/// <param name="day">天</param>
|
||||||
/// <returns>统计信息</returns>
|
/// <returns>统计信息</returns>
|
||||||
public async Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day)
|
public async Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day)
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
//取最近七天
|
//取最近七天
|
||||||
|
@@ -10,13 +10,9 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
using TouchSocket.Dmtp.Rpc;
|
|
||||||
using TouchSocket.Rpc;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
public interface IBackendLogService
|
||||||
public interface IBackendLogService : IRpcServer
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 删除 BackendLog 表中的所有记录
|
/// 删除 BackendLog 表中的所有记录
|
||||||
@@ -24,29 +20,25 @@ public interface IBackendLogService : IRpcServer
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 调用此方法会删除 BackendLog 表中的所有记录。
|
/// 调用此方法会删除 BackendLog 表中的所有记录。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DmtpRpc]
|
|
||||||
Task DeleteBackendLogAsync();
|
Task DeleteBackendLogAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取最新的十条 BackendLog 记录
|
/// 获取最新的十条 BackendLog 记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>最新的十条记录</returns>
|
/// <returns>最新的十条记录</returns>
|
||||||
[DmtpRpc]
|
Task<List<BackendLog>> GetNewBackendLog();
|
||||||
Task<List<BackendLog>> GetNewLog();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询 BackendLog 数据
|
/// 分页查询 BackendLog 数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="option">查询选项</param>
|
/// <param name="option">查询选项</param>
|
||||||
/// <returns>查询到的数据</returns>
|
/// <returns>查询到的数据</returns>
|
||||||
[DmtpRpc]
|
Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option);
|
||||||
Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取最近一段时间内每天的后端日志统计信息
|
/// 获取最近一段时间内每天的后端日志统计信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="day">要统计的天数</param>
|
/// <param name="day">要统计的天数</param>
|
||||||
/// <returns>按天统计的后端日志信息列表</returns>
|
/// <returns>按天统计的后端日志信息列表</returns>
|
||||||
[DmtpRpc]
|
Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day);
|
||||||
Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
|
|
||||||
}
|
}
|
||||||
|
@@ -26,19 +26,19 @@ public interface IRpcLogService
|
|||||||
/// 获取最新的十条 RpcLog 记录
|
/// 获取最新的十条 RpcLog 记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>最新的十条记录</returns>
|
/// <returns>最新的十条记录</returns>
|
||||||
Task<List<RpcLog>> GetNewLog();
|
Task<List<RpcLog>> GetNewRpcLog();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询 RpcLog 数据
|
/// 分页查询 RpcLog 数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="option">查询选项</param>
|
/// <param name="option">查询选项</param>
|
||||||
/// <returns>查询到的数据</returns>
|
/// <returns>查询到的数据</returns>
|
||||||
Task<QueryData<RpcLog>> PageAsync(QueryPageOptions option);
|
Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按天统计 RpcLog 数据
|
/// 按天统计 RpcLog 数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="day">统计的天数</param>
|
/// <param name="day">统计的天数</param>
|
||||||
/// <returns>按天统计的结果列表</returns>
|
/// <returns>按天统计的结果列表</returns>
|
||||||
Task<List<RpcLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
|
Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day);
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最新十条
|
/// 最新十条
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<List<RpcLog>> GetNewLog()
|
public async Task<List<RpcLog>> GetNewRpcLog()
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
var data = await db.Queryable<RpcLog>().OrderByDescending(a => a.LogTime).Take(10).ToListAsync().ConfigureAwait(false);
|
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>
|
/// </summary>
|
||||||
/// <param name="option">查询条件</param>
|
/// <param name="option">查询条件</param>
|
||||||
public Task<QueryData<RpcLog>> PageAsync(QueryPageOptions option)
|
public Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option)
|
||||||
{
|
{
|
||||||
return QueryAsync(option);
|
return QueryAsync(option);
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
|
|||||||
|
|
||||||
#endregion 删除
|
#endregion 删除
|
||||||
|
|
||||||
public async Task<List<RpcLogDayStatisticsOutput>> StatisticsByDayAsync(int day)
|
public async Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day)
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
//取最近七天
|
//取最近七天
|
||||||
|
@@ -8,12 +8,11 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
public interface IGatewayMonitorHostedService : IHostedService
|
public interface IGatewayMonitorHostedService
|
||||||
{
|
{
|
||||||
public ILogger Logger { get; }
|
public ILogger Logger { get; }
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -8,20 +8,14 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using ThingsGateway.Authentication;
|
||||||
using Microsoft.Extensions.Localization;
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
namespace ThingsGateway.UpgradeServer;
|
public interface IAuthenticationService
|
||||||
|
|
||||||
public partial class NotFound404
|
|
||||||
{
|
{
|
||||||
[Inject]
|
Task<string> UUID();
|
||||||
[NotNull]
|
Task<AuthorizeInfo> TryAuthorize(string password);
|
||||||
private IStringLocalizer<NotFound404>? Localizer { get; set; }
|
Task<AuthorizeInfo> TryGetAuthorizeInfo();
|
||||||
|
Task UnAuthorize();
|
||||||
[Inject]
|
|
||||||
[NotNull]
|
|
||||||
private NavigationManager? NavigationManager { get; set; }
|
|
||||||
}
|
}
|
@@ -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);
|
||||||
|
|
||||||
|
}
|
@@ -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();
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
@@ -11,21 +11,21 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
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 clientManagementOptions = App.GetOptions<RemoteClientManagementOptions>();
|
||||||
var serverManagementOptions = App.GetOptions<RemoteServerManagementOptions>();
|
var serverManagementOptions = App.GetOptions<RemoteServerManagementOptions>();
|
||||||
RemoteClientManagementTask = new RemoteManagementTask(loggerFactory.CreateLogger(nameof(RemoteClientManagementTask)), clientManagementOptions);
|
RemoteClientManagementTask = new ManagementTask(loggerFactory.CreateLogger(nameof(RemoteClientManagementTask)), clientManagementOptions);
|
||||||
RemoteServerManagementTask = new RemoteManagementTask(loggerFactory.CreateLogger(nameof(RemoteServerManagementTask)), serverManagementOptions);
|
RemoteServerManagementTask = new ManagementTask(loggerFactory.CreateLogger(nameof(RemoteServerManagementTask)), serverManagementOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemoteManagementTask RemoteClientManagementTask;
|
private ManagementTask RemoteClientManagementTask;
|
||||||
private RemoteManagementTask RemoteServerManagementTask;
|
private ManagementTask RemoteServerManagementTask;
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
@@ -12,8 +12,8 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
|
|
||||||
using ThingsGateway.ConfigurableOptions;
|
using ThingsGateway.ConfigurableOptions;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
public class RemoteManagementOptions
|
public class ManagementOptions
|
||||||
{
|
{
|
||||||
public bool Enable { get; set; }
|
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 override bool IsServer => false;
|
||||||
}
|
}
|
||||||
public class RemoteServerManagementOptions : RemoteManagementOptions, IConfigurableOptions
|
public class RemoteServerManagementOptions : ManagementOptions, IConfigurableOptions
|
||||||
{
|
{
|
||||||
public override bool IsServer => true;
|
public override bool IsServer => true;
|
||||||
}
|
}
|
@@ -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);
|
||||||
|
}
|
@@ -8,26 +8,26 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
using TouchSocket.Dmtp;
|
using TouchSocket.Dmtp;
|
||||||
|
using TouchSocket.Dmtp.FileTransfer;
|
||||||
using TouchSocket.Dmtp.Rpc;
|
using TouchSocket.Dmtp.Rpc;
|
||||||
using TouchSocket.Rpc;
|
using TouchSocket.Rpc;
|
||||||
using TouchSocket.Sockets;
|
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 ILog LogMessage;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private TextFileLogger TextLogger;
|
private TextFileLogger TextLogger;
|
||||||
|
|
||||||
public RemoteManagementTask(ILogger logger, RemoteManagementOptions remoteManagementOptions)
|
public ManagementTask(ILogger logger, ManagementOptions managementOptions)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
|
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
|
||||||
@@ -37,7 +37,7 @@ public partial class RemoteManagementTask : AsyncDisposableObject
|
|||||||
log?.AddLogger(TextLogger);
|
log?.AddLogger(TextLogger);
|
||||||
LogMessage = log;
|
LogMessage = log;
|
||||||
|
|
||||||
_remoteManagementOptions = remoteManagementOptions;
|
_managementOptions = managementOptions;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +48,9 @@ public partial class RemoteManagementTask : AsyncDisposableObject
|
|||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
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);
|
_tcpDmtpService ??= await GetTcpDmtpService().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -78,33 +78,52 @@ public partial class RemoteManagementTask : AsyncDisposableObject
|
|||||||
|
|
||||||
private TcpDmtpClient? _tcpDmtpClient;
|
private TcpDmtpClient? _tcpDmtpClient;
|
||||||
private TcpDmtpService? _tcpDmtpService;
|
private TcpDmtpService? _tcpDmtpService;
|
||||||
private RemoteManagementOptions _remoteManagementOptions;
|
private ManagementOptions _managementOptions;
|
||||||
|
|
||||||
|
|
||||||
private async Task<TcpDmtpClient> GetTcpDmtpClient()
|
private async Task<TcpDmtpClient> GetTcpDmtpClient()
|
||||||
{
|
{
|
||||||
var tcpDmtpClient = new TcpDmtpClient();
|
var tcpDmtpClient = new TcpDmtpClient();
|
||||||
var config = new TouchSocketConfig()
|
var config = new TouchSocketConfig()
|
||||||
.SetRemoteIPHost(_remoteManagementOptions.ServerUri)
|
.SetRemoteIPHost(_managementOptions.ServerUri)
|
||||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||||
.SetDmtpOption(new DmtpOption() { VerifyToken = _remoteManagementOptions.VerifyToken })
|
.SetDmtpOption(new DmtpOption() { VerifyToken = _managementOptions.VerifyToken })
|
||||||
.ConfigureContainer(a =>
|
.ConfigureContainer(a =>
|
||||||
{
|
{
|
||||||
|
a.AddDmtpRouteService();//添加路由策略
|
||||||
a.AddLogger(LogMessage);
|
a.AddLogger(LogMessage);
|
||||||
a.AddRpcStore(store => store.RegisterServer<RemoteManagementRpcServer>());
|
a.AddRpcStore(store =>
|
||||||
|
{
|
||||||
|
store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer());
|
||||||
|
store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer());
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
.ConfigurePlugins(a =>
|
.ConfigurePlugins(a =>
|
||||||
{
|
{
|
||||||
a.UseTcpSessionCheckClear();
|
a.UseTcpSessionCheckClear();
|
||||||
a.UseDmtpRpc();
|
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
|
||||||
|
{
|
||||||
|
b.UseSystemTextJson(json =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
a.UseDmtpFileTransfer();//必须添加文件传输插件
|
||||||
|
|
||||||
|
a.Add<FilePlugin>();
|
||||||
|
|
||||||
|
|
||||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||||
.SetTick(TimeSpan.FromMilliseconds(_remoteManagementOptions.HeartbeatInterval))
|
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
|
||||||
.SetMaxFailCount(3);
|
.SetMaxFailCount(3);
|
||||||
|
|
||||||
a.AddDmtpHandshakedPlugin(async () =>
|
a.AddDmtpHandshakedPlugin(async () =>
|
||||||
{
|
{
|
||||||
try
|
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)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -121,20 +140,37 @@ public partial class RemoteManagementTask : AsyncDisposableObject
|
|||||||
{
|
{
|
||||||
var tcpDmtpService = new TcpDmtpService();
|
var tcpDmtpService = new TcpDmtpService();
|
||||||
var config = new TouchSocketConfig()
|
var config = new TouchSocketConfig()
|
||||||
.SetListenIPHosts(_remoteManagementOptions.ServerUri)
|
.SetListenIPHosts(_managementOptions.ServerUri)
|
||||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||||
.SetDmtpOption(new DmtpOption() { VerifyToken = _remoteManagementOptions.VerifyToken })
|
.SetDmtpOption(new DmtpOption() { VerifyToken = _managementOptions.VerifyToken })
|
||||||
.ConfigureContainer(a =>
|
.ConfigureContainer(a =>
|
||||||
{
|
{
|
||||||
|
a.AddDmtpRouteService();//添加路由策略
|
||||||
a.AddLogger(LogMessage);
|
a.AddLogger(LogMessage);
|
||||||
a.AddRpcStore(store => store.RegisterServer<RemoteManagementRpcServer>());
|
a.AddRpcStore(store =>
|
||||||
|
{
|
||||||
|
store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer());
|
||||||
|
store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer());
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
.ConfigurePlugins(a =>
|
.ConfigurePlugins(a =>
|
||||||
{
|
{
|
||||||
a.UseTcpSessionCheckClear();
|
a.UseTcpSessionCheckClear();
|
||||||
a.UseDmtpRpc();
|
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
|
||||||
|
{
|
||||||
|
b.UseSystemTextJson(json =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
a.UseDmtpFileTransfer();//必须添加文件传输插件
|
||||||
|
|
||||||
|
a.Add<FilePlugin>();
|
||||||
|
|
||||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||||
.SetTick(TimeSpan.FromMilliseconds(_remoteManagementOptions.HeartbeatInterval))
|
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
|
||||||
.SetMaxFailCount(3);
|
.SetMaxFailCount(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -144,7 +180,7 @@ public partial class RemoteManagementTask : AsyncDisposableObject
|
|||||||
|
|
||||||
private async Task EnsureChannelOpenAsync(CancellationToken cancellationToken)
|
private async Task EnsureChannelOpenAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_remoteManagementOptions.IsServer)
|
if (_managementOptions.IsServer)
|
||||||
{
|
{
|
||||||
if (_tcpDmtpService.ServerState != ServerState.Running)
|
if (_tcpDmtpService.ServerState != ServerState.Running)
|
||||||
{
|
{
|
||||||
@@ -176,6 +212,18 @@ public partial class RemoteManagementTask : AsyncDisposableObject
|
|||||||
_tcpDmtpService.SafeDispose();
|
_tcpDmtpService.SafeDispose();
|
||||||
_tcpDmtpService = null;
|
_tcpDmtpService = null;
|
||||||
}
|
}
|
||||||
|
TextLogger?.Dispose();
|
||||||
await base.DisposeAsync(disposing).ConfigureAwait(false);
|
await base.DisposeAsync(disposing).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -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";
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
using TouchSocket.Dmtp;
|
using TouchSocket.Dmtp;
|
||||||
using TouchSocket.Dmtp.FileTransfer;
|
using TouchSocket.Dmtp.FileTransfer;
|
||||||
|
|
||||||
namespace ThingsGateway.Upgrade;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
internal sealed class FilePlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTransferredPlugin, IDmtpRoutingPlugin
|
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)
|
public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e)
|
||||||
{
|
{
|
||||||
e.IsPermitOperation = true;//允许路由
|
e.IsPermitOperation = true;//允许路由
|
||||||
m_logger.Info($"路由类型:{e.RouterType}");
|
m_logger.Debug($"路由类型:{e.RouterType}");
|
||||||
await e.InvokeNext().ConfigureAwait(false);
|
await e.InvokeNext().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace ThingsGateway.Upgrade;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
public class UpdateZipFile
|
public class UpdateZipFile
|
||||||
{
|
{
|
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -12,13 +12,13 @@
|
|||||||
using TouchSocket.Dmtp.Rpc;
|
using TouchSocket.Dmtp.Rpc;
|
||||||
using TouchSocket.Rpc;
|
using TouchSocket.Rpc;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
|
#if Management
|
||||||
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
||||||
|
#endif
|
||||||
public interface IUpgradeRpcServer : IRpcServer
|
public interface IUpgradeRpcServer : IRpcServer
|
||||||
{
|
{
|
||||||
[DmtpRpc]
|
[DmtpRpc]
|
||||||
void Restart();
|
Task Upgrade(ICallContext callContext, List<UpdateZipFile> updateZipFiles);
|
||||||
[DmtpRpc]
|
|
||||||
Task Upgrade();
|
|
||||||
}
|
}
|
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@@ -15,10 +15,10 @@ using System.Diagnostics;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using ThingsGateway.Gateway.Application;
|
||||||
using ThingsGateway.NewLife;
|
using ThingsGateway.NewLife;
|
||||||
using ThingsGateway.NewLife.Extension;
|
using ThingsGateway.NewLife.Extension;
|
||||||
using ThingsGateway.NewLife.Threading;
|
using ThingsGateway.NewLife.Threading;
|
||||||
using ThingsGateway.Upgrade;
|
|
||||||
|
|
||||||
namespace ThingsGateway;
|
namespace ThingsGateway;
|
||||||
|
|
||||||
@@ -29,8 +29,8 @@ public static class RestartServerHelper
|
|||||||
//删除不必要的文件
|
//删除不必要的文件
|
||||||
DeleteDelEx();
|
DeleteDelEx();
|
||||||
//删除备份
|
//删除备份
|
||||||
Delete(FileConst.BackupPath);
|
Delete(FileConst.UpgradeBackupPath);
|
||||||
Delete(FileConst.BackupDirPath);
|
Delete(FileConst.UpgradeBackupDirPath);
|
||||||
Delete(FileConst.UpgradePath);
|
Delete(FileConst.UpgradePath);
|
||||||
|
|
||||||
//备份原数据
|
//备份原数据
|
||||||
@@ -234,8 +234,8 @@ public static class RestartServerHelper
|
|||||||
{
|
{
|
||||||
//备份原数据
|
//备份原数据
|
||||||
var backupDir = new DirectoryInfo(AppContext.BaseDirectory);
|
var backupDir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
backupDir.CopyTo(FileConst.BackupDirPath, allSub: true);
|
backupDir.CopyTo(FileConst.UpgradeBackupDirPath, allSub: true);
|
||||||
FileConst.BackupDirPath.AsDirectory().Compress(FileConst.BackupPath);
|
FileConst.UpgradeBackupDirPath.AsDirectory().Compress(FileConst.UpgradeBackupPath);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
@@ -8,9 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
|
||||||
|
|
||||||
public class GatewayRedundantSerivce : IGatewayRedundantSerivce
|
public class GatewayRedundantSerivce : IGatewayRedundantSerivce
|
||||||
{
|
{
|
||||||
|
@@ -8,16 +8,15 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Microsoft.Extensions.Hosting;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
public interface IRedundancyHostedService
|
||||||
|
|
||||||
public interface IRedundancyHostedService : IHostedService
|
|
||||||
{
|
{
|
||||||
Task StartTaskAsync(CancellationToken cancellationToken);
|
Task StartRedundancyTaskAsync();
|
||||||
Task StopTaskAsync();
|
Task StopRedundancyTaskAsync();
|
||||||
Task ForcedSync(CancellationToken cancellationToken = default);
|
Task RedundancyForcedSync();
|
||||||
|
|
||||||
public TextFileLogger TextLogger { get; }
|
public Task<TouchSocket.Core.LogLevel> RedundancyLogLevel();
|
||||||
public string LogPath { get; }
|
public Task SetRedundancyLogLevel(TouchSocket.Core.LogLevel logLevel);
|
||||||
|
public Task<string> RedundancyLogPath();
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
public interface IRedundancyService
|
public interface IRedundancyService
|
||||||
{
|
{
|
||||||
|
@@ -8,12 +8,10 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
|
||||||
|
|
||||||
using TouchSocket.Dmtp.Rpc;
|
using TouchSocket.Dmtp.Rpc;
|
||||||
using TouchSocket.Rpc;
|
using TouchSocket.Rpc;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
||||||
internal interface IRedundantRpcServer : IRpcServer
|
internal interface IRedundantRpcServer : IRpcServer
|
||||||
@@ -26,13 +24,3 @@ internal interface IRedundantRpcServer : IRpcServer
|
|||||||
[DmtpRpc]
|
[DmtpRpc]
|
||||||
void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas);
|
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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
//}
|
|
@@ -10,9 +10,7 @@
|
|||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
|
||||||
|
|
||||||
public class DeviceDataWithValue
|
public class DeviceDataWithValue
|
||||||
{
|
{
|
||||||
|
@@ -10,9 +10,7 @@
|
|||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
|
||||||
|
|
||||||
public class VariableDataWithValue
|
public class VariableDataWithValue
|
||||||
{
|
{
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService
|
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService
|
||||||
{
|
{
|
||||||
@@ -24,23 +24,37 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
|||||||
}
|
}
|
||||||
private RedundancyTask RedundancyTask;
|
private RedundancyTask RedundancyTask;
|
||||||
|
|
||||||
public TextFileLogger TextLogger => RedundancyTask.TextLogger;
|
|
||||||
|
|
||||||
public string LogPath => RedundancyTask.LogPath;
|
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await RedundancyTask.DisposeAsync().ConfigureAwait(false);
|
await RedundancyTask.DisposeAsync().ConfigureAwait(false);
|
||||||
await base.StopAsync(cancellationToken).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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 冗余配置
|
/// 冗余配置
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using ThingsGateway.NewLife.Extension;
|
using ThingsGateway.NewLife.Extension;
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
internal sealed class RedundancyService : BaseService<SysDict>, IRedundancyService
|
internal sealed class RedundancyService : BaseService<SysDict>, IRedundancyService
|
||||||
{
|
{
|
||||||
|
@@ -14,7 +14,6 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
using ThingsGateway.Gateway.Application;
|
|
||||||
using ThingsGateway.NewLife;
|
using ThingsGateway.NewLife;
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
@@ -24,7 +23,7 @@ using TouchSocket.Rpc;
|
|||||||
using TouchSocket.Rpc.Generators;
|
using TouchSocket.Rpc.Generators;
|
||||||
using TouchSocket.Sockets;
|
using TouchSocket.Sockets;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||||
{
|
{
|
||||||
@@ -238,9 +237,9 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
return GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.ReadOnlyIdChannels.Values);
|
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();
|
RedundancyOptions = (await _redundancyService.GetRedundancyAsync().ConfigureAwait(false)).AdaptRedundancyOptions();
|
||||||
|
|
||||||
if (RedundancyOptions?.Enable == true)
|
if (RedundancyOptions?.Enable == true)
|
||||||
@@ -278,7 +277,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
scheduledTask.Start();
|
scheduledTask.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task StopTaskAsync()
|
public async Task StopRedundancyTaskAsync()
|
||||||
{
|
{
|
||||||
if (scheduledTask?.Enable == true)
|
if (scheduledTask?.Enable == true)
|
||||||
{
|
{
|
||||||
@@ -310,7 +309,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
await StopTaskAsync().ConfigureAwait(false);
|
await StopRedundancyTaskAsync().ConfigureAwait(false);
|
||||||
TextLogger?.TryDispose();
|
TextLogger?.TryDispose();
|
||||||
scheduledTask?.SafeDispose();
|
scheduledTask?.SafeDispose();
|
||||||
|
|
||||||
@@ -336,13 +335,22 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
.ConfigureContainer(a =>
|
.ConfigureContainer(a =>
|
||||||
{
|
{
|
||||||
a.AddLogger(LogMessage);
|
a.AddLogger(LogMessage);
|
||||||
a.AddRpcStore(store => store.RegisterServer(typeof(IRedundantRpcServer), new RedundantRpcServer(this)));
|
a.AddRpcStore(store =>
|
||||||
|
{
|
||||||
|
store.RegisterServer<IRedundantRpcServer>(new RedundantRpcServer(this));
|
||||||
|
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.ConfigurePlugins(a =>
|
.ConfigurePlugins(a =>
|
||||||
{
|
{
|
||||||
a.UseTcpSessionCheckClear();
|
a.UseTcpSessionCheckClear();
|
||||||
|
|
||||||
a.UseDmtpRpc();
|
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
|
||||||
|
{
|
||||||
|
b.UseSystemTextJson(json =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
});
|
||||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||||
@@ -367,12 +375,21 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
.ConfigureContainer(a =>
|
.ConfigureContainer(a =>
|
||||||
{
|
{
|
||||||
a.AddLogger(LogMessage);
|
a.AddLogger(LogMessage);
|
||||||
a.AddRpcStore(store => store.RegisterServer(typeof(IRedundantRpcServer), new RedundantRpcServer(this)));
|
a.AddRpcStore(store =>
|
||||||
|
{
|
||||||
|
store.RegisterServer<IRedundantRpcServer>(new RedundantRpcServer(this));
|
||||||
|
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.ConfigurePlugins(a =>
|
.ConfigurePlugins(a =>
|
||||||
{
|
{
|
||||||
a.UseTcpSessionCheckClear();
|
a.UseTcpSessionCheckClear();
|
||||||
a.UseDmtpRpc();
|
a.UseDmtpRpc().ConfigureDefaultSerializationSelector(b =>
|
||||||
|
{
|
||||||
|
b.UseSystemTextJson(json =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
});
|
||||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||||
@@ -396,10 +413,10 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ForcedSync
|
#region RedundancyForcedSync
|
||||||
|
|
||||||
WaitLock ForcedSyncWaitLock = new WaitLock(nameof(RedundancyTask));
|
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);
|
await ForcedSyncWaitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
@@ -420,7 +437,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
|
|
||||||
if (!online)
|
if (!online)
|
||||||
{
|
{
|
||||||
LogMessage?.LogWarning("ForcedSync data error, no client online");
|
LogMessage?.LogWarning("RedundancyForcedSync data error, no client online");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +456,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogMessage?.LogWarning(ex, "ForcedSync data error");
|
LogMessage?.LogWarning(ex, "RedundancyForcedSync data error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -488,7 +505,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
|||||||
await client.GetDmtpRpcActor().SyncDataAsync(channelBatch.ToList(), deviceBatch.ToList(), variableBatch, invokeOption).ConfigureAwait(false);
|
await client.GetDmtpRpcActor().SyncDataAsync(channelBatch.ToList(), deviceBatch.ToList(), variableBatch, invokeOption).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogMessage?.LogTrace($"ForcedSync data success");
|
LogMessage?.LogTrace($"RedundancyForcedSync data success");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@@ -8,14 +8,12 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
using TouchSocket.Dmtp.Rpc;
|
using TouchSocket.Dmtp.Rpc;
|
||||||
using TouchSocket.Rpc;
|
using TouchSocket.Rpc;
|
||||||
using TouchSocket.Sockets;
|
using TouchSocket.Sockets;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
internal sealed partial class RedundantRpcServer : SingletonRpcServer, IRedundantRpcServer
|
internal sealed partial class RedundantRpcServer : SingletonRpcServer, IRedundantRpcServer
|
||||||
{
|
{
|
||||||
|
@@ -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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -8,11 +8,9 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
global using BootstrapBlazor.Components;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
global using Microsoft.AspNetCore.Components;
|
public interface IRestartService
|
||||||
global using Microsoft.Extensions.Localization;
|
{
|
||||||
|
Task RestartServer();
|
||||||
global using System.Diagnostics.CodeAnalysis;
|
}
|
||||||
|
|
||||||
global using ThingsGateway.Razor;
|
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
|
||||||
}
|
|
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,7 +11,7 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
internal sealed class WebApiHostedService : BackgroundService
|
internal sealed class WebApiHostedService : BackgroundService
|
||||||
{
|
{
|
||||||
|
@@ -12,7 +12,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
|
|
||||||
using ThingsGateway.ConfigurableOptions;
|
using ThingsGateway.ConfigurableOptions;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
public class WebApiOptions : IConfigurableOptions
|
public class WebApiOptions : IConfigurableOptions
|
||||||
{
|
{
|
||||||
public bool Enable { get; set; }
|
public bool Enable { get; set; }
|
||||||
|
@@ -12,15 +12,13 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application;
|
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
using TouchSocket.Http;
|
using TouchSocket.Http;
|
||||||
using TouchSocket.Rpc;
|
using TouchSocket.Rpc;
|
||||||
using TouchSocket.Sockets;
|
using TouchSocket.Sockets;
|
||||||
using TouchSocket.WebApi.Swagger;
|
using TouchSocket.WebApi.Swagger;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
public partial class WebApiTask : AsyncDisposableObject
|
public partial class WebApiTask : AsyncDisposableObject
|
||||||
{
|
{
|
||||||
@@ -143,6 +141,7 @@ public partial class WebApiTask : AsyncDisposableObject
|
|||||||
_httpService = null;
|
_httpService = null;
|
||||||
}
|
}
|
||||||
await base.DisposeAsync(disposing).ConfigureAwait(false);
|
await base.DisposeAsync(disposing).ConfigureAwait(false);
|
||||||
|
TextLogger?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -30,3 +30,20 @@ public class PluginAddInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<IBrowserFile> OtherFiles { get; set; }
|
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();
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
@@ -15,11 +15,20 @@ namespace ThingsGateway.Gateway.Application;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 驱动插件服务
|
/// 驱动插件服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPluginService
|
public interface IPluginService : IPluginPageService
|
||||||
{
|
{
|
||||||
Type GetDebugUI(string pluginName);
|
Type GetDebugUI(string pluginName);
|
||||||
Type GetAddressUI(string pluginName);
|
Type GetAddressUI(string pluginName);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据插件类型获取信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
List<PluginInfo> GetPluginListSync(PluginTypeEnum? pluginType = null);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据插件全名称构建插件实例
|
/// 根据插件全名称构建插件实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -40,12 +49,7 @@ public interface IPluginService
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
(IEnumerable<IEditorItem> EditorItems, object Model, Type PropertyUIType) GetDriverPropertyTypes(string pluginName, IDriver? driver = null);
|
(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>
|
/// <summary>
|
||||||
/// 获取变量属性
|
/// 获取变量属性
|
||||||
@@ -55,22 +59,6 @@ public interface IPluginService
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
(IEnumerable<IEditorItem> EditorItems, object Model, Type VariablePropertyUIType) GetVariablePropertyTypes(string pluginName, BusinessBase? businessBase = null);
|
(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>
|
/// <summary>
|
||||||
/// 设置插件动态属性
|
/// 设置插件动态属性
|
||||||
|
@@ -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}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -35,7 +35,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DirName = "GatewayPlugins";
|
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 SaveEx = ".save";
|
||||||
private const string DelEx = ".del";
|
private const string DelEx = ".del";
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private System.Collections.ObjectModel.ReadOnlyDictionary<string, Type> _defaultDriverBaseDict { get; }
|
private System.Collections.ObjectModel.ReadOnlyDictionary<string, Type> _defaultDriverBaseDict { get; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 插件FullName/插件Type
|
/// 插件FullName/插件Type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -100,7 +101,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
_locker.Wait();
|
_locker.Wait();
|
||||||
|
|
||||||
// 解析插件名称,获取文件名和类型名
|
// 解析插件名称,获取文件名和类型名
|
||||||
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(pluginName);
|
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(pluginName);
|
||||||
|
|
||||||
// 如果是默认键,则搜索主程序上下文中的类型
|
// 如果是默认键,则搜索主程序上下文中的类型
|
||||||
if (_defaultDriverBaseDict.TryGetValue(pluginName, out var type))
|
if (_defaultDriverBaseDict.TryGetValue(pluginName, out var type))
|
||||||
@@ -258,7 +259,29 @@ internal sealed class PluginService : IPluginService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginType">要筛选的插件类型,可选参数</param>
|
/// <param name="pluginType">要筛选的插件类型,可选参数</param>
|
||||||
/// <returns>符合条件的插件列表</returns>
|
/// <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();
|
var pluginList = PrivateGetList();
|
||||||
@@ -274,7 +297,6 @@ internal sealed class PluginService : IPluginService
|
|||||||
|
|
||||||
return filteredPlugins;
|
return filteredPlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取变量的属性类型
|
/// 获取变量的属性类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -308,21 +330,21 @@ internal sealed class PluginService : IPluginService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页显示插件
|
/// 分页显示插件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public QueryData<PluginInfo> Page(QueryPageOptions options, PluginTypeEnum? pluginType = null)
|
public async Task<QueryData<PluginInfo>> PluginPage(QueryPageOptions options, PluginTypeEnum? pluginType = null)
|
||||||
{
|
{
|
||||||
//指定关键词搜索为插件FullName
|
//指定关键词搜索为插件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;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 移除全部插件
|
/// 移除全部插件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Reload()
|
public async Task ReloadPlugin()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_locker.Wait();
|
await _locker.WaitAsync().ConfigureAwait(false);
|
||||||
_driverBaseDict.Clear();
|
_driverBaseDict.Clear();
|
||||||
foreach (var item in _assemblyLoadContextDict)
|
foreach (var item in _assemblyLoadContextDict)
|
||||||
{
|
{
|
||||||
@@ -335,53 +357,56 @@ internal sealed class PluginService : IPluginService
|
|||||||
{
|
{
|
||||||
_locker.Release();
|
_locker.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private WaitLock SaveLock = new(nameof(PluginService));
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步保存驱动程序信息。
|
/// 异步保存驱动程序信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plugin">要保存的插件信息。</param>
|
/// <param name="plugin">要保存的插件信息。</param>
|
||||||
[OperDesc("SavePlugin", isRecordPar: false, localizerType: typeof(PluginAddInput))]
|
[OperDesc("SavePlugin", isRecordPar: false, localizerType: typeof(PluginAddInput))]
|
||||||
public async Task SavePlugin(PluginAddInput plugin)
|
public async Task SavePluginByPath(PluginAddPathInput plugin)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 等待锁可用
|
// 等待锁可用
|
||||||
await _locker.WaitAsync().ConfigureAwait(false);
|
await SaveLock.WaitAsync().ConfigureAwait(false);
|
||||||
|
var tempDir = DirName;
|
||||||
// 创建程序集加载上下文
|
// 创建程序集加载上下文
|
||||||
var assemblyLoadContext = new AssemblyLoadContext(CommonUtils.GetSingleId().ToString(), true);
|
var assemblyLoadContext = new AssemblyLoadContext(CommonUtils.GetSingleId().ToString(), true);
|
||||||
// 存储其他文件的内存流列表
|
// 存储其他文件的内存流列表
|
||||||
List<(string Name, MemoryStream MemoryStream)> otherFilesStreams = new();
|
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;
|
string fullDir = string.Empty;
|
||||||
bool isDefaultDriver = false;
|
bool isDefaultDriver = false;
|
||||||
//判定是否上下文程序集
|
//判定是否上下文程序集
|
||||||
var defaultDriver = _defaultDriverBaseDict.FirstOrDefault(a => Path.GetFileNameWithoutExtension(new FileInfo(a.Value.Assembly.Location).Name) == mainFileName);
|
var defaultDriver = _defaultDriverBaseDict.FirstOrDefault(a => Path.GetFileNameWithoutExtension(new FileInfo(a.Value.Assembly.Location).Name) == mainFileName);
|
||||||
if (defaultDriver.Value != null)
|
if (defaultDriver.Value != null)
|
||||||
{
|
{
|
||||||
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(defaultDriver.Key);
|
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(defaultDriver.Key);
|
||||||
fullDir = Path.GetDirectoryName(filtResult.FileName);
|
fullDir = Path.GetDirectoryName(filtResult.FileName);
|
||||||
isDefaultDriver = true;
|
isDefaultDriver = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 构建插件文件夹绝对路径
|
// 构建插件文件夹绝对路径
|
||||||
fullDir = AppContext.BaseDirectory.CombinePathWithOs(DirName, mainFileName);
|
fullDir = AppContext.BaseDirectory.CombinePathWithOs(tempDir, mainFileName);
|
||||||
isDefaultDriver = false;
|
isDefaultDriver = false;
|
||||||
}
|
}
|
||||||
|
// 获取主程序集文件流
|
||||||
|
MemoryStream mainMemoryStream = new MemoryStream();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 构建主程序集绝对路径
|
// 构建主程序集绝对路径
|
||||||
var fullPath = fullDir.CombinePathWithOs(plugin.MainFile.Name);
|
var fullPath = fullDir.CombinePathWithOs(Path.GetFileName(plugin.MainFilePath));
|
||||||
|
|
||||||
// 获取主程序集文件流
|
|
||||||
using var stream = plugin.MainFile.OpenReadStream(maxFileSize);
|
using var stream = File.Open(plugin.MainFilePath, FileMode.Open, FileAccess.Read);
|
||||||
MemoryStream mainMemoryStream = new MemoryStream();
|
|
||||||
await stream.CopyToAsync(mainMemoryStream).ConfigureAwait(false);
|
await stream.CopyToAsync(mainMemoryStream).ConfigureAwait(false);
|
||||||
mainMemoryStream.Seek(0, SeekOrigin.Begin);
|
mainMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
@@ -389,14 +414,14 @@ internal sealed class PluginService : IPluginService
|
|||||||
// 先加载到内存,如果成功添加后再装载到文件
|
// 先加载到内存,如果成功添加后再装载到文件
|
||||||
// 加载主程序集
|
// 加载主程序集
|
||||||
var assembly = assemblyLoadContext.LoadFromStream(mainMemoryStream);
|
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();
|
MemoryStream memoryStream = new MemoryStream();
|
||||||
await otherStream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
await otherStream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
otherFilesStreams.Add((item.Name, memoryStream));
|
otherFilesStreams.Add((Path.GetFileName(item), memoryStream));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 尝试加载附属程序集
|
// 尝试加载附属程序集
|
||||||
@@ -451,6 +476,16 @@ internal sealed class PluginService : IPluginService
|
|||||||
}
|
}
|
||||||
finally
|
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();
|
assemblyLoadContext.Unload();
|
||||||
|
|
||||||
@@ -469,7 +504,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// 释放锁资源
|
// 释放锁资源
|
||||||
_locker.Release();
|
SaveLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +618,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
if (_assemblyLoadContextDict.TryGetValue(path, out var assemblyLoadContext))
|
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);
|
_assemblyLoadContextDict.Remove(path);
|
||||||
//卸载
|
//卸载
|
||||||
assemblyLoadContext.AssemblyLoadContext.Unload();
|
assemblyLoadContext.AssemblyLoadContext.Unload();
|
||||||
@@ -713,6 +748,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
{
|
{
|
||||||
var plugins = new List<PluginInfo>();
|
var plugins = new List<PluginInfo>();
|
||||||
// 主程序上下文
|
// 主程序上下文
|
||||||
|
string tempDir = DirName;
|
||||||
|
|
||||||
// 遍历程序集上下文默认驱动字典,生成默认驱动插件信息
|
// 遍历程序集上下文默认驱动字典,生成默认驱动插件信息
|
||||||
foreach (var item in _defaultDriverBaseDict)
|
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)
|
foreach (string folderPath in folderPaths)
|
||||||
@@ -769,7 +805,7 @@ internal sealed class PluginService : IPluginService
|
|||||||
{
|
{
|
||||||
//添加到字典
|
//添加到字典
|
||||||
_driverBaseDict.TryAdd($"{driverMainName}.{type.Name}", type);
|
_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()
|
var plugin = new PluginInfo()
|
||||||
{
|
{
|
||||||
@@ -789,10 +825,15 @@ internal sealed class PluginService : IPluginService
|
|||||||
catch (Exception ex)
|
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);
|
return plugins.DistinctBy(a => a.FullName).OrderBy(a => a.EducationPlugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//public Task SavePlugin(PluginAddInput plugin)
|
||||||
|
//{
|
||||||
|
// return PluginInfoUtil.SavePlugin(plugin);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
@@ -67,44 +67,6 @@ public static class PluginServiceUtil
|
|||||||
return cols;
|
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)
|
public static bool HasDynamicProperty(object model)
|
||||||
{
|
{
|
||||||
|
@@ -8,13 +8,11 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
using ThingsGateway.Blazor.Diagrams.Core;
|
using ThingsGateway.Blazor.Diagrams.Core;
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
public interface IRulesEngineHostedService : IHostedService
|
public interface IRulesEngineHostedService
|
||||||
{
|
{
|
||||||
Dictionary<RulesLog, Diagram> Diagrams { get; }
|
Dictionary<RulesLog, Diagram> Diagrams { get; }
|
||||||
Task Delete(IEnumerable<long> ids);
|
Task Delete(IEnumerable<long> ids);
|
||||||
|
@@ -97,7 +97,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
|||||||
private (RulesLog rulesLog, DefaultDiagram blazorDiagram) Init(Rules rules)
|
private (RulesLog rulesLog, DefaultDiagram blazorDiagram) Init(Rules rules)
|
||||||
{
|
{
|
||||||
#pragma warning disable CA1863
|
#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
|
#pragma warning restore CA1863
|
||||||
log.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
log.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
||||||
DefaultDiagram blazorDiagram = new();
|
DefaultDiagram blazorDiagram = new();
|
||||||
|
@@ -594,7 +594,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
// 设备页导入预览输出
|
// 设备页导入预览输出
|
||||||
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
|
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();
|
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
||||||
|
|
||||||
// 遍历每个工作表
|
// 遍历每个工作表
|
||||||
|
@@ -103,7 +103,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
|
|||||||
foreach (var plugin in pluginDrivers.Keys.Distinct())
|
foreach (var plugin in pluginDrivers.Keys.Distinct())
|
||||||
{
|
{
|
||||||
var filtered = FilterPluginDevices(data, plugin, deviceDicts, channelDicts);
|
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));
|
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())
|
foreach (var plugin in pluginDrivers.Keys.Distinct())
|
||||||
{
|
{
|
||||||
var filtered = FilterPluginDevices(data, plugin, deviceDicts, channelDicts);
|
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));
|
sheets.Add(pluginName, GetPluginSheets(filtered, deviceDicts, channelDicts, plugin, pluginDrivers, propertysDict));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
|
|||||||
}
|
}
|
||||||
var deviceDicts = GlobalData.IdDevices;
|
var deviceDicts = GlobalData.IdDevices;
|
||||||
var channelDicts = GlobalData.IdChannels;
|
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();
|
Dictionary<string, object> sheets = new();
|
||||||
//变量页
|
//变量页
|
||||||
@@ -436,7 +436,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
|
|||||||
if (!driverPluginDicts.ContainsKey(channel.PluginName))
|
if (!driverPluginDicts.ContainsKey(channel.PluginName))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var pluginName = PluginServiceUtil.GetFileNameAndTypeName(channel.PluginName);
|
var pluginName = PluginInfoUtil.GetFileNameAndTypeName(channel.PluginName);
|
||||||
//lock (devicePropertys)
|
//lock (devicePropertys)
|
||||||
{
|
{
|
||||||
if (devicePropertys.ContainsKey(pluginName.Item2))
|
if (devicePropertys.ContainsKey(pluginName.Item2))
|
||||||
@@ -510,10 +510,10 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
|
|||||||
|
|
||||||
// 设备页导入预览输出
|
// 设备页导入预览输出
|
||||||
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
|
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
|
||||||
|
var plugins = GlobalData.PluginService.GetPluginListSync();
|
||||||
// 获取驱动插件的全名和名称的字典
|
// 获取驱动插件的全名和名称的字典
|
||||||
var driverPluginFullNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.FullName);
|
var driverPluginFullNameDict = plugins.ToDictionary(a => a.FullName);
|
||||||
var driverPluginNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.Name);
|
var driverPluginNameDict = plugins.ToDictionary(a => a.Name);
|
||||||
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
||||||
|
|
||||||
var sheetNames = uSheetDatas.sheets.Keys.ToList();
|
var sheetNames = uSheetDatas.sheets.Keys.ToList();
|
||||||
|
@@ -14,8 +14,6 @@ using Microsoft.Extensions.Logging;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
using ThingsGateway.Authentication;
|
using ThingsGateway.Authentication;
|
||||||
using ThingsGateway.Management;
|
|
||||||
using ThingsGateway.Upgrade;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
@@ -35,16 +33,15 @@ public class Startup : AppStartup
|
|||||||
|
|
||||||
services.AddSingleton<IRedundancyService, RedundancyService>();
|
services.AddSingleton<IRedundancyService, RedundancyService>();
|
||||||
services.AddGatewayHostedService<IRedundancyHostedService, RedundancyHostedService>();
|
services.AddGatewayHostedService<IRedundancyHostedService, RedundancyHostedService>();
|
||||||
services.AddGatewayHostedService<IUpdateZipFileHostedService, UpdateZipFileHostedService>();
|
|
||||||
|
|
||||||
services.AddHostedService<RemoteManagementHostedService>();
|
services.AddHostedService<ManagementHostedService>();
|
||||||
services.AddHostedService<WebApiHostedService>();
|
services.AddHostedService<WebApiHostedService>();
|
||||||
|
|
||||||
services.AddSingleton<GatewayRedundantSerivce>();
|
services.AddSingleton<GatewayRedundantSerivce>();
|
||||||
services.AddSingleton<IGatewayRedundantSerivce>(provider => provider.GetRequiredService<GatewayRedundantSerivce>());
|
services.AddSingleton<IGatewayRedundantSerivce>(provider => provider.GetRequiredService<GatewayRedundantSerivce>());
|
||||||
services.AddConfigurableOptions<UpgradeServerOptions>();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
services.AddSingleton<ITextFileReadService, TextFileReadService>();
|
||||||
|
|
||||||
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
|
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
|
||||||
|
|
||||||
@@ -81,9 +78,14 @@ public class Startup : AppStartup
|
|||||||
services.AddSingleton<IDeviceService, DeviceService>();
|
services.AddSingleton<IDeviceService, DeviceService>();
|
||||||
services.AddSingleton<IDeviceRuntimeService, DeviceRuntimeService>();
|
services.AddSingleton<IDeviceRuntimeService, DeviceRuntimeService>();
|
||||||
services.AddSingleton<IPluginService, PluginService>();
|
services.AddSingleton<IPluginService, PluginService>();
|
||||||
|
services.AddSingleton<IPluginPageService>(a => a.GetService<IPluginService>());
|
||||||
|
|
||||||
services.AddSingleton<IBackendLogService, BackendLogService>();
|
services.AddSingleton<IBackendLogService, BackendLogService>();
|
||||||
services.AddSingleton<IRpcLogService, RpcLogService>();
|
services.AddSingleton<IRpcLogService, RpcLogService>();
|
||||||
services.AddSingleton<IRpcService, RpcService>();
|
services.AddSingleton<IRpcService, RpcService>();
|
||||||
|
services.AddSingleton<IAuthenticationService, AuthenticationService>();
|
||||||
|
services.AddSingleton<IRestartService, RestartService>();
|
||||||
|
services.AddSingleton<IChannelEnableService, ChannelEnableService>();
|
||||||
|
|
||||||
services.AddGatewayHostedService<IAlarmHostedService, AlarmHostedService>();
|
services.AddGatewayHostedService<IAlarmHostedService, AlarmHostedService>();
|
||||||
services.AddGatewayHostedService<IGatewayMonitorHostedService, GatewayMonitorHostedService>();
|
services.AddGatewayHostedService<IGatewayMonitorHostedService, GatewayMonitorHostedService>();
|
||||||
|
@@ -50,18 +50,4 @@
|
|||||||
<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams.Core\ThingsGateway.Blazor.Diagrams.Core.csproj" />
|
<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams.Core\ThingsGateway.Blazor.Diagrams.Core.csproj" />
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
"ThingsGateway.Management.Authentication": {
|
"ThingsGateway.Gateway.Razor.Authentication": {
|
||||||
"AuthName": "AuthName",
|
"AuthName": "AuthName",
|
||||||
"Authorized": "Authorized",
|
"Authorized": "Authorized",
|
||||||
"ExpireTime": "ExpireTime",
|
"ExpireTime": "ExpireTime",
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
"ActualMax": "实际最大值"
|
"ActualMax": "实际最大值"
|
||||||
},
|
},
|
||||||
|
|
||||||
"ThingsGateway.Management.Authentication": {
|
"ThingsGateway.Gateway.Razor.Authentication": {
|
||||||
"AuthName": "公司名称",
|
"AuthName": "公司名称",
|
||||||
"Authorized": "已授权",
|
"Authorized": "已授权",
|
||||||
"ExpireTime": "过期时间",
|
"ExpireTime": "过期时间",
|
||||||
|
@@ -58,8 +58,10 @@ public partial class ChannelEditComponent
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
PluginNames = GlobalData.PluginService.GetList(PluginType).BuildPluginSelectList();
|
var plugins = GlobalData.PluginService.GetPluginListSync(PluginType);
|
||||||
PluginDcit = GlobalData.PluginService.GetList(PluginType).ToDictionary(a => a.FullName);
|
|
||||||
|
PluginDcit = plugins.ToDictionary(a => a.FullName);
|
||||||
|
PluginNames = plugins.BuildPluginSelectList();
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
}
|
}
|
||||||
protected override Task OnParametersSetAsync()
|
protected override Task OnParametersSetAsync()
|
||||||
|
@@ -56,7 +56,7 @@ public partial class GatewayMonitorPage
|
|||||||
}
|
}
|
||||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
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)
|
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);
|
VariableRuntimes = channels.Where(a => a.PluginName == pluginName).SelectMany(a => a.ReadDeviceRuntimes).SelectMany(a => a.Value.ReadOnlyVariableRuntimes).Select(a => a.Value).Where(a => a != null);
|
||||||
|
@@ -237,7 +237,7 @@
|
|||||||
GlobalData.ReadOnlyIdDevices.TryGetValue(item.Key, out var items);
|
GlobalData.ReadOnlyIdDevices.TryGetValue(item.Key, out var items);
|
||||||
}
|
}
|
||||||
<div class="flex-fill">
|
<div class="flex-fill">
|
||||||
@($"{items.Name} - {PluginServiceUtil.GetFileNameAndTypeName(items?.PluginName).TypeName}")
|
@($"{items.Name} - {PluginInfoUtil.GetFileNameAndTypeName(items?.PluginName).TypeName}")
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button OnClick=@((a)=>
|
<Button OnClick=@((a)=>
|
||||||
|
@@ -1,10 +1,17 @@
|
|||||||
@namespace ThingsGateway.Gateway.Razor
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
@page "/gateway/backendlog"
|
|
||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
|
@{
|
||||||
|
#if !Management
|
||||||
|
}
|
||||||
|
@page "/gateway/backendlog"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@attribute [RolePermission]
|
@attribute [RolePermission]
|
||||||
|
|
||||||
|
@{
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@inherits ComponentDefault
|
@inherits ComponentDefault
|
||||||
|
|
||||||
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" />
|
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" />
|
||||||
|
@@ -26,7 +26,7 @@ public partial class BackendLogPage
|
|||||||
if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested) return (new ChartDataSource());
|
if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested) return (new ChartDataSource());
|
||||||
if (ChartDataSource == null)
|
if (ChartDataSource == null)
|
||||||
{
|
{
|
||||||
var dayStatisticsOutputs = await BackendLogService.StatisticsByDayAsync(7);
|
var dayStatisticsOutputs = await BackendLogService.BackendLogStatisticsByDayAsync(7);
|
||||||
ChartDataSource = new ChartDataSource();
|
ChartDataSource = new ChartDataSource();
|
||||||
ChartDataSource.Options.Title = Localizer[nameof(BackendLog)];
|
ChartDataSource.Options.Title = Localizer[nameof(BackendLog)];
|
||||||
ChartDataSource.Options.X.Title = Localizer["Date"];
|
ChartDataSource.Options.X.Title = Localizer["Date"];
|
||||||
@@ -59,7 +59,7 @@ public partial class BackendLogPage
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dayStatisticsOutputs = await BackendLogService.StatisticsByDayAsync(7);
|
var dayStatisticsOutputs = await BackendLogService.BackendLogStatisticsByDayAsync(7);
|
||||||
ChartDataSource.Labels = dayStatisticsOutputs.Select(a => a.Date);
|
ChartDataSource.Labels = dayStatisticsOutputs.Select(a => a.Date);
|
||||||
ChartDataSource.Data[0].Data = dayStatisticsOutputs.Select(a => (object)a.DebugCount);
|
ChartDataSource.Data[0].Data = dayStatisticsOutputs.Select(a => (object)a.DebugCount);
|
||||||
ChartDataSource.Data[1].Data = dayStatisticsOutputs.Select(a => (object)a.InfoCount);
|
ChartDataSource.Data[1].Data = dayStatisticsOutputs.Select(a => (object)a.InfoCount);
|
||||||
@@ -79,7 +79,7 @@ public partial class BackendLogPage
|
|||||||
{
|
{
|
||||||
return await Task.Run(async () =>
|
return await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var data = await BackendLogService.PageAsync(options);
|
var data = await BackendLogService.BackendLogPageAsync(options);
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,19 @@
|
|||||||
@namespace ThingsGateway.Gateway.Razor
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
@page "/gateway/rpclog"
|
|
||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
|
|
||||||
|
@{
|
||||||
|
#if !Management
|
||||||
|
}
|
||||||
|
@page "/gateway/rpclog"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@attribute [RolePermission]
|
@attribute [RolePermission]
|
||||||
|
|
||||||
|
@{
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
@inherits ComponentDefault
|
@inherits ComponentDefault
|
||||||
|
|
||||||
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" />
|
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" />
|
||||||
|
@@ -26,7 +26,7 @@ public partial class RpcLogPage
|
|||||||
if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested) return (new ChartDataSource());
|
if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested) return (new ChartDataSource());
|
||||||
if (ChartDataSource == null)
|
if (ChartDataSource == null)
|
||||||
{
|
{
|
||||||
var dayStatisticsOutputs = await RpcLogService.StatisticsByDayAsync(7);
|
var dayStatisticsOutputs = await RpcLogService.RpcLogStatisticsByDayAsync(7);
|
||||||
ChartDataSource = new ChartDataSource();
|
ChartDataSource = new ChartDataSource();
|
||||||
ChartDataSource.Options.Title = Localizer[nameof(RpcLog)];
|
ChartDataSource.Options.Title = Localizer[nameof(RpcLog)];
|
||||||
ChartDataSource.Options.X.Title = Localizer["Date"];
|
ChartDataSource.Options.X.Title = Localizer["Date"];
|
||||||
@@ -47,7 +47,7 @@ public partial class RpcLogPage
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dayStatisticsOutputs = await RpcLogService.StatisticsByDayAsync(7);
|
var dayStatisticsOutputs = await RpcLogService.RpcLogStatisticsByDayAsync(7);
|
||||||
ChartDataSource.Labels = dayStatisticsOutputs.Select(a => a.Date);
|
ChartDataSource.Labels = dayStatisticsOutputs.Select(a => a.Date);
|
||||||
ChartDataSource.Data[0].Data = dayStatisticsOutputs.Select(a => (object)a.SuccessCount);
|
ChartDataSource.Data[0].Data = dayStatisticsOutputs.Select(a => (object)a.SuccessCount);
|
||||||
ChartDataSource.Data[1].Data = dayStatisticsOutputs.Select(a => (object)a.FailCount);
|
ChartDataSource.Data[1].Data = dayStatisticsOutputs.Select(a => (object)a.FailCount);
|
||||||
@@ -65,7 +65,7 @@ public partial class RpcLogPage
|
|||||||
{
|
{
|
||||||
return await Task.Run(async () =>
|
return await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var data = await RpcLogService.PageAsync(options);
|
var data = await RpcLogService.RpcLogPageAsync(options);
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
@namespace ThingsGateway.Management
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
@using ThingsGateway
|
@using ThingsGateway
|
||||||
@using ThingsGateway.Authentication
|
@using ThingsGateway.Authentication
|
||||||
@if (WebsiteOption.Value.ShowAuthorize)
|
@if (WebsiteOption.Value.ShowAuthorize)
|
||||||
@@ -14,8 +14,10 @@
|
|||||||
|
|
||||||
@Localizer["UUID"]
|
@Localizer["UUID"]
|
||||||
</label>
|
</label>
|
||||||
|
@if (AuthorizeInfo != null)
|
||||||
<Textarea Value="@ProAuthentication.UUID" rows="5" />
|
{
|
||||||
|
<Textarea Value="@AuthorizeInfo.Uuid" rows="5" />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ using Microsoft.Extensions.Options;
|
|||||||
|
|
||||||
using ThingsGateway.Authentication;
|
using ThingsGateway.Authentication;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public partial class Authentication
|
public partial class Authentication
|
||||||
@@ -30,20 +30,21 @@ public partial class Authentication
|
|||||||
private AuthorizeInfo AuthorizeInfo { get; set; }
|
private AuthorizeInfo AuthorizeInfo { get; set; }
|
||||||
[Inject]
|
[Inject]
|
||||||
ToastService ToastService { get; set; }
|
ToastService ToastService { get; set; }
|
||||||
|
[Inject]
|
||||||
protected override void OnParametersSet()
|
IAuthenticationService AuthenticationService { get; set; }
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
|
var authorizeInfo = await AuthenticationService.TryGetAuthorizeInfo();
|
||||||
AuthorizeInfo = authorizeInfo;
|
AuthorizeInfo = authorizeInfo;
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Register()
|
private async Task Register()
|
||||||
{
|
{
|
||||||
var result = ProAuthentication.TryAuthorize(Password, out var authorizeInfo);
|
var result = await AuthenticationService.TryAuthorize(Password);
|
||||||
if (result)
|
if (result.Auth)
|
||||||
{
|
{
|
||||||
AuthorizeInfo = authorizeInfo;
|
AuthorizeInfo = result;
|
||||||
await ToastService.Default();
|
await ToastService.Default();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -54,8 +55,8 @@ public partial class Authentication
|
|||||||
}
|
}
|
||||||
private async Task Unregister()
|
private async Task Unregister()
|
||||||
{
|
{
|
||||||
ProAuthentication.UnAuthorize();
|
await AuthenticationService.UnAuthorize();
|
||||||
_ = ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
|
var authorizeInfo = await AuthenticationService.TryGetAuthorizeInfo();
|
||||||
AuthorizeInfo = authorizeInfo;
|
AuthorizeInfo = authorizeInfo;
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
@@ -1,16 +1,14 @@
|
|||||||
@using BootstrapBlazor.Components
|
@using BootstrapBlazor.Components
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Upgrade
|
|
||||||
@using ThingsGateway.Debug
|
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Razor
|
@using ThingsGateway.Razor
|
||||||
@namespace ThingsGateway.Management
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
|
|
||||||
<span class=@("text-h6 mx-1") style="overflow:auto;">
|
<span class=@("text-h6 mx-1") style="overflow:auto;">
|
||||||
<span class="text-truncate">
|
<span class="text-truncate">
|
||||||
@RedundancyLocalizer["Status"]:@(GlobalData.StartCollectChannelEnable ? RedundancyLocalizer["Master"] : RedundancyLocalizer["Slave"])
|
@RedundancyLocalizer["Status"]:@(StartCollectChannelEnable ? RedundancyLocalizer["Master"] : RedundancyLocalizer["Slave"])
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
|
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class RedundancyOptionsHeader : IDisposable
|
public partial class RedundancyOptionsHeader : IDisposable
|
||||||
{
|
{
|
||||||
@@ -22,14 +22,22 @@ public partial class RedundancyOptionsHeader : IDisposable
|
|||||||
{
|
{
|
||||||
Disposed = true;
|
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 () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
while (!Disposed)
|
while (!Disposed)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
StartCollectChannelEnable = await ChannelEnableService.StartCollectChannelEnable();
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -38,6 +46,6 @@ public partial class RedundancyOptionsHeader : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,31 +2,34 @@
|
|||||||
@using BootstrapBlazor.Components
|
@using BootstrapBlazor.Components
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Debug
|
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Razor
|
@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>
|
||||||
<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>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Components.Web;
|
|||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class RedundancyOptionsPage
|
public partial class RedundancyOptionsPage
|
||||||
{
|
{
|
||||||
@@ -34,17 +34,37 @@ public partial class RedundancyOptionsPage
|
|||||||
private RedundancyOptions Model { get; set; }
|
private RedundancyOptions Model { get; set; }
|
||||||
|
|
||||||
[Parameter, EditorRequired]
|
[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]
|
[Inject]
|
||||||
[NotNull]
|
[NotNull]
|
||||||
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
|
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await base.OnInitializedAsync();
|
|
||||||
HeaderText = GatewayLocalizer[nameof(HeaderText)];
|
HeaderText = GatewayLocalizer[nameof(HeaderText)];
|
||||||
Model = (await RedundancyService.GetRedundancyAsync()).AdaptRedundancyOptions();
|
Model = (await RedundancyService.GetRedundancyAsync()).AdaptRedundancyOptions();
|
||||||
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
@@ -55,6 +75,9 @@ public partial class RedundancyOptionsPage
|
|||||||
[NotNull]
|
[NotNull]
|
||||||
private IRedundancyService? RedundancyService { get; set; }
|
private IRedundancyService? RedundancyService { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private SwalService? SwalService { get; set; }
|
private SwalService? SwalService { get; set; }
|
||||||
@@ -72,8 +95,8 @@ public partial class RedundancyOptionsPage
|
|||||||
await RedundancyService.EditRedundancyOptionAsync(Model);
|
await RedundancyService.EditRedundancyOptionAsync(Model);
|
||||||
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Save"]}{RazorLocalizer["Success"]}");
|
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Save"]}{RazorLocalizer["Success"]}");
|
||||||
|
|
||||||
await RedundancyHostedService.StopTaskAsync();
|
await RedundancyHostedService.StopRedundancyTaskAsync();
|
||||||
await RedundancyHostedService.StartTaskAsync(CancellationToken.None);
|
await RedundancyHostedService.StartRedundancyTaskAsync();
|
||||||
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Success"]}");
|
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Success"]}");
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
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()
|
var ret = await SwalService.ShowModal(new SwalOption()
|
||||||
{
|
{
|
||||||
@@ -94,7 +117,7 @@ public partial class RedundancyOptionsPage
|
|||||||
});
|
});
|
||||||
if (ret)
|
if (ret)
|
||||||
{
|
{
|
||||||
await RedundancyHostedService.ForcedSync();
|
await RedundancyHostedService.RedundancyForcedSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,22 @@
|
|||||||
@page "/gateway/system"
|
@{
|
||||||
|
#if !Management
|
||||||
|
}
|
||||||
|
@page "/gateway/system"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@attribute [RolePermission]
|
@attribute [RolePermission]
|
||||||
|
|
||||||
|
@{
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@inherits ComponentDefault
|
@inherits ComponentDefault
|
||||||
@using BootstrapBlazor.Components
|
@using BootstrapBlazor.Components
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Authentication
|
@using ThingsGateway.Authentication
|
||||||
@using ThingsGateway.Upgrade
|
|
||||||
@using ThingsGateway.Debug
|
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Razor
|
@using ThingsGateway.Razor
|
||||||
@namespace ThingsGateway.Management
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
|
|
||||||
|
|
||||||
<div class="appconfig">
|
<div class="appconfig">
|
||||||
@@ -29,7 +34,7 @@
|
|||||||
|
|
||||||
<BodyTemplate>
|
<BodyTemplate>
|
||||||
|
|
||||||
<RedundancyOptionsPage Logger="@RedundancyHostedService.TextLogger" LogPath="@RedundancyHostedService.LogPath" />
|
<RedundancyOptionsPage LogLevel="LogLevel" LogLevelChanged=@(SetLogLevel) LogPath="@LogPath" />
|
||||||
|
|
||||||
</BodyTemplate>
|
</BodyTemplate>
|
||||||
|
|
||||||
@@ -37,19 +42,6 @@
|
|||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Text=@GatewayLocalizer["CheckUpdate"]>
|
|
||||||
|
|
||||||
<Card class="h-100">
|
|
||||||
|
|
||||||
<BodyTemplate>
|
|
||||||
<UpdateZipFilePage></UpdateZipFilePage>
|
|
||||||
</BodyTemplate>
|
|
||||||
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
|
|
||||||
<TabItem Text=@GatewayLocalizer["Restart"]>
|
<TabItem Text=@GatewayLocalizer["Restart"]>
|
||||||
|
|
||||||
<Card class="h-100">
|
<Card class="h-100">
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
|
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace ThingsGateway.Management;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class SystemConfigPage
|
public partial class SystemConfigPage
|
||||||
{
|
{
|
||||||
@@ -27,11 +27,23 @@ public partial class SystemConfigPage
|
|||||||
[NotNull]
|
[NotNull]
|
||||||
private SwalService? SwalService { get; set; }
|
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)
|
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
|
||||||
TabItem?.SetHeader(AppContext.TitleLocalizer["系统管理"]);
|
|
||||||
|
|
||||||
if (firstRender && Tab != null && tabComponent != null)
|
if (firstRender && Tab != null && tabComponent != null)
|
||||||
{
|
{
|
||||||
tabComponent.ActiveTab(Tab.Value);
|
tabComponent.ActiveTab(Tab.Value);
|
||||||
@@ -49,6 +61,8 @@ public partial class SystemConfigPage
|
|||||||
[NotNull]
|
[NotNull]
|
||||||
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
|
public IStringLocalizer<ThingsGateway.Gateway.Razor._Imports> GatewayLocalizer { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
public IRestartService RestartService { get; set; }
|
||||||
private async Task OnRestart()
|
private async Task OnRestart()
|
||||||
{
|
{
|
||||||
var result = await SwalService.ShowModal(new SwalOption()
|
var result = await SwalService.ShowModal(new SwalOption()
|
||||||
@@ -58,7 +72,7 @@ public partial class SystemConfigPage
|
|||||||
});
|
});
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
RestartServerHelper.RestartServer();
|
await RestartService.RestartServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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>
|
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -26,7 +26,7 @@ public partial class PluginDebugPage
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
var pluginInfos = PluginService.GetList().AdaptListPluginInfo();
|
var pluginInfos = PluginService.GetPluginListSync().AdaptListPluginInfo();
|
||||||
|
|
||||||
foreach (var pluginInfo in pluginInfos.ToList())
|
foreach (var pluginInfo in pluginInfos.ToList())
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,14 @@
|
|||||||
@page "/gateway/plugin"
|
|
||||||
|
@{
|
||||||
|
#if !Management
|
||||||
|
}
|
||||||
|
@page "/gateway/plugin"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@attribute [RolePermission]
|
@attribute [RolePermission]
|
||||||
|
|
||||||
|
@{
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@namespace ThingsGateway.Gateway.Razor
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
@using BootstrapBlazor.Components
|
@using BootstrapBlazor.Components
|
||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
|
@@ -16,7 +16,7 @@ public partial class PluginPage
|
|||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private IPluginService? PluginService { get; set; }
|
private IPluginPageService? PluginService { get; set; }
|
||||||
|
|
||||||
private PluginInfo SearchModel { get; set; } = new();
|
private PluginInfo SearchModel { get; set; } = new();
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ public partial class PluginPage
|
|||||||
{
|
{
|
||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
var data = PluginService.Page(options);
|
var data = PluginService.PluginPage(options);
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -69,14 +69,14 @@ public partial class PluginPage
|
|||||||
};
|
};
|
||||||
op.Component = BootstrapDynamicComponent.CreateComponent<SavePlugin>(new Dictionary<string, object?>
|
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);
|
await DialogService.Show(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnReload()
|
private Task OnReload()
|
||||||
{
|
{
|
||||||
PluginService.Reload();
|
return PluginService.ReloadPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion 添加
|
#endregion 添加
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user