Compare commits

...

5 Commits

Author SHA1 Message Date
Kimdiego2098
f82c5f2f27 3.0.0.6 2023-10-11 16:35:10 +08:00
Kimdiego2098
a83c1c3899 update nuget 2023-10-11 16:28:18 +08:00
Kimdiego2098
91d6aed109 调整代码执行顺序 2023-10-11 16:19:34 +08:00
Kimdiego2098
db8f8fe51d update ManageGateway 2023-10-11 10:59:33 +08:00
Kimdiego2098
4596004b17 update ManageGateway 2023-10-11 10:41:09 +08:00
24 changed files with 1569 additions and 69 deletions

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.5</Version>
<Version>3.0.0.6</Version>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Authors>Diego</Authors>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.5</Version>
<Version>3.0.0.6</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net45;netstandard2.0;net6.0;net7.0</TargetFrameworks>

View File

@@ -25,7 +25,7 @@ public abstract class ReadWriteDevicesSerialSessionBase : ReadWriteDevicesBase
{
SerialSession = serialSession;
WaitingClientEx = SerialSession.GetWaitingClientEx(new() { BreakTrigger = true });
SerialSession.Received += Received;
SerialSession.Received -= Received;
SerialSession.Connecting -= Connecting;
SerialSession.Connected -= Connected;
SerialSession.Disconnecting -= Disconnecting;
@@ -34,6 +34,7 @@ public abstract class ReadWriteDevicesSerialSessionBase : ReadWriteDevicesBase
SerialSession.Connected += Connected;
SerialSession.Disconnecting += Disconnecting;
SerialSession.Disconnected += Disconnected;
SerialSession.Received += Received;
Logger = SerialSession.Logger;
}
/// <summary>
@@ -86,11 +87,12 @@ public abstract class ReadWriteDevicesSerialSessionBase : ReadWriteDevicesBase
/// <inheritdoc/>
public override void Dispose()
{
Disconnect();
SerialSession.Received -= Received;
SerialSession.Connecting -= Connecting;
SerialSession.Connected -= Connected;
SerialSession.Disconnecting -= Disconnecting;
SerialSession.Disconnected -= Disconnected;
Disconnect();
if (CascadeDisposal)
SerialSession.SafeDispose();
}

View File

@@ -72,11 +72,11 @@ public abstract class ReadWriteDevicesTcpClientBase : ReadWriteDevicesBase
/// <inheritdoc/>
public override void Dispose()
{
Disconnect();
TcpClient.Connecting -= Connecting;
TcpClient.Connected -= Connected;
TcpClient.Disconnecting -= Disconnecting;
TcpClient.Disconnected -= Disconnected;
Disconnect();
if (CascadeDisposal)
TcpClient.SafeDispose();
}

View File

@@ -23,9 +23,14 @@ public abstract class ReadWriteDevicesTcpServerBase : ReadWriteDevicesBase
public ReadWriteDevicesTcpServerBase(TcpService tcpService)
{
TcpService = tcpService;
TcpService.Received -= Received;
TcpService.Connecting -= Connecting;
TcpService.Connected -= Connected;
TcpService.Disconnecting -= Disconnecting;
TcpService.Disconnected -= Disconnected;
TcpService.Received += Received;
TcpService.Connecting += Connecting;
TcpService.Connected += Connected;
TcpService.Received += Received;
TcpService.Disconnecting += Disconnecting;
TcpService.Disconnected += Disconnected;
Logger = TcpService.Logger;
@@ -63,11 +68,12 @@ public abstract class ReadWriteDevicesTcpServerBase : ReadWriteDevicesBase
/// <inheritdoc/>
public override void Dispose()
{
Disconnect();
TcpService.Received -= Received;
TcpService.Connecting -= Connecting;
TcpService.Connected -= Connected;
TcpService.Disconnecting -= Disconnecting;
TcpService.Disconnected -= Disconnected;
Disconnect();
if (CascadeDisposal)
TcpService.SafeDispose();
}

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.5</Version>
<Version>3.0.0.6</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.5</Version>
<Version>3.0.0.6</Version>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Authors>Diego</Authors>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.5</Version>
<Version>3.0.0.6</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>

View File

@@ -29,33 +29,33 @@
else
{
<MRow NoGutters Style="height:100%">
<MCol Md=5 Sm=12>
<MSheet Elevation=1 Style="width:100%; height:100%;" Class="d-flex align-start flex-column mb-6">
<div class="d-flex align-center ml-12 mt-12">
<MAvatar Size="40" Color="teal">
<span class="white--text text-h6">@CONFIG_TITLE?.GetNameLen2()</span>
</MAvatar>
<h1>@CONFIG_TITLE</h1>
</div>
<div class="d-flex align-center ml-12 mt-12 mb-auto">
<h3>@CONFIG_REMARK</h3>
</div>
<div class="d-flex align-center pa-2" style="width:100%;height:100%;">
<MImage Src=@(BlazorResourceConst.ResourceUrl+"images/login-left.svg")></MImage>
</div>
</MSheet>
<MCol Md=7 Sm=12>
<MSheet Elevation=1 Style="width:100%; height:100%;" Class="d-flex align-start flex-column mb-6">
<div class="d-flex align-center ml-12 mt-12">
<MAvatar Size="40" Color="teal">
<span class="white--text text-h6">@CONFIG_TITLE?.GetNameLen2()</span>
</MAvatar>
<h1>@CONFIG_TITLE</h1>
</div>
<div class="d-flex align-center ml-12 mt-12 mb-auto">
<h3>@CONFIG_REMARK</h3>
</div>
<div class="d-flex align-center pa-2" style="width:100%;height:100%;">
<MImage Src=@(BlazorResourceConst.ResourceUrl+"images/login-left.svg")></MImage>
</div>
</MSheet>
</MCol>
</MCol>
<MCol Md=7 Sm=12 Align="AlignTypes.Center">
<MRow Md=6 Sm=12 Justify="JustifyTypes.Center" Align="AlignTypes.Center">
<MCard Class="px-16 py-12" @onkeydown=Enter>
@GetLoginCore()
</MCard>
</MRow>
</MCol>
<MCol Md=5 Sm=12 Align="AlignTypes.Center">
<MRow Md=6 Sm=12 Justify="JustifyTypes.Center" Align="AlignTypes.Center">
<MCard Class="px-16 py-12" @onkeydown=Enter>
@GetLoginCore()
</MCard>
</MRow>
</MCol>
</MRow>
</MRow>
}
@@ -73,30 +73,29 @@ else
<MTextField TValue="string"
Label=账号
Outlined
HideDetails="@("auto")"
@bind-Value=@loginModel.Account>
</MTextField>
<MTextField TValue="string"
Class="mt-10"
Label="密码"
Type="@(_showPassword ? "text" : "password")"
AppendIcon="@(_showPassword ? "mdi-eye" : "mdi-eye-off")"
OnAppendClick="()=>_showPassword = !_showPassword"
HideDetails="@("auto")"
@bind-Value=@loginModel.Account>
</MTextField>
<MTextField TValue="string"
Class="mt-5"
Label="密码"
Type="@(_showPassword ? "text" : "password")"
AppendIcon="@(_showPassword ? "mdi-eye" : "mdi-eye-off")"
OnAppendClick="()=>_showPassword = !_showPassword"
Outlined
HideDetails="@("auto")"
@bind-Value=@Password>
</MTextField>
@if (_showCaptcha)
HideDetails="@("auto")"
@bind-Value=@Password>
</MTextField>
@if (_showCaptcha)
{
<PImageCaptcha @ref=captcha @bind-Value="CaptchaValue"
TextFieldClass="mt-10 mx-auto"
Height="60"
Label=验证码 Outlined
OnRefresh="RefreshCode"
ErrorMessage=验证码错误>
</PImageCaptcha>
TextFieldClass="mt-5 mx-auto"
Label=验证码 Outlined Dense
OnRefresh="RefreshCode"
ErrorMessage=验证码错误>
</PImageCaptcha>
}
<MButton Class="mt-11 rounded-4" OnClick=LoginAsync Height=46 Width=@("100%") Color="primary">登录</MButton>
<MButton Class="mt-8 rounded-4" OnClick=LoginAsync Height=45 Width=@("100%") Color="primary">登录</MButton>
</div>
;
return ViewSubMenu;
@@ -190,8 +189,8 @@ else
protected override async Task OnParametersSetAsync()
{
#if DEBUG
loginModel.Account = "superAdmin";
Password = "111111";
loginModel.Account = "superAdmin";
Password = "111111";
#endif
GetCaptchaInfo();
CONFIG_TITLE = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_TITLE))?.ConfigValue;

View File

@@ -53,7 +53,7 @@
</MAppBar>
<MMain Style=@($"{(!(IsMobile||_drawerOpen!=true)? "padding-left:200px;":"")}")>
<MMain Style=@($"{(!(IsMobile||_drawerOpen!=true)? "padding-left:200px;":"")}")>
<div class="full-width">
<PageTabs @ref="_pageTabs" PageTabItems="PageTabItems" />
</div>
@@ -63,7 +63,7 @@
@Body
</PPageContainer>
</MCard>
<MSheet Class="d-flex justify-center align-center rounded-0" Style=@($"height: {BlazorResourceConst.FooterHeight}px;")>
<MSheet Class="d-flex justify-center align-center rounded-0" Style=@($"height: {BlazorResourceConst.FooterHeight}px; ")>
<Foter CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_COPYRIGHT_URL=@CONFIG_COPYRIGHT_URL CONFIG_TITLE=@CONFIG_TITLE></Foter>
</MSheet>
</MMain>

View File

@@ -2,7 +2,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.12" />
</ItemGroup>

View File

@@ -5,10 +5,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.8.45" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.45" />
<PackageReference Include="Furion.Pure" Version="4.8.8.45" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.108" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.8.48" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.48" />
<PackageReference Include="Furion.Pure" Version="4.8.8.48" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.110" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@@ -16,6 +16,7 @@ using Masa.Blazor;
using Masa.Blazor.Presets;
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.Components;
/// <summary>

View File

@@ -2,7 +2,8 @@
<ItemGroup>
<PackageReference Include="Masa.Blazor" Version="1.0.4" />
<PackageReference Include="Masa.Blazor" Version="1.1.0" />
<PackageReference Include="Masa.Blazor.SomethingSkia" Version="1.1.0" />
</ItemGroup>

View File

@@ -95,6 +95,23 @@
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001904",
"Title": "管理网关",
"Icon": "mdi-database-sync-outline",
"Component": "/gatewayconfig/manage",
"Category": "MENU",
"ParentId": "200001",
"SortCode": "3",
"TargetType": "SELF",
"CreateTime": "2023-02-26 01:02:12.089",
"CreateUser": "superAdmin",
"CreateUserId": "212725263002001",
"IsDelete": false,
"UpdateTime": "2023-03-03 18:01:49.2309339",
"UpdateUser": "superAdmin",
"UpdateUserId": "212725263002001"
},
{
"Id": "200001004",
"Title": "运行状态",

View File

@@ -46,6 +46,7 @@ public class Startup : AppStartup
services.AddHostedService<HistoryValueWorker>();
services.AddHostedService<UploadDeviceWorker>();
services.AddHostedService<UpgradeWorker>();
services.AddHostedService<ManageGatewayWorker>();
}

View File

@@ -0,0 +1,192 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using System.ComponentModel;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// ManageGatewayConfig
/// </summary>
public class ManageGatewayConfig
{
/// <summary>
/// 启用
/// </summary>
[Description("启用")]
public bool Enable { get; set; }
/// <summary>
/// MqttBrokerIP
/// </summary>
[Description("Mqtt-Tcp IP")]
public string MqttBrokerIP { get; set; }
/// <summary>
/// MqttBrokerPort
/// </summary>
[Description("Mqtt-Tcp 端口")]
public int MqttBrokerPort { get; set; }
/// <summary>
/// UserName
/// </summary>
[Description("Mqtt用户名")]
public string UserName { get; set; }
/// <summary>
/// Password
/// </summary>
[Description("Mqtt密码")]
public string Password { get; set; }
/// <summary>
/// WriteRpcTopicRpc返回为{WriteRpcTopic}/Return只有这个topic才开放外部订阅权限
/// </summary>
[Description("变量写入Rpc主题")]
public string WriteRpcTopic { get; set; }
/// <summary>
/// DBDownTopic
/// </summary>
[Description("配置下发Rpc主题")]
public string DBDownTopic { get; set; }
/// <summary>
/// DBUploadTopic
/// </summary>
[Description("配置上传Rpc主题")]
public string DBUploadTopic { get; set; }
}
/// <summary>
/// ClientGatewayConfig
/// </summary>
public class ClientGatewayConfig : ManageGatewayConfig
{
/// <summary>
/// 标识
/// </summary>
[Description("子网关标识ID")]
public string GatewayId { get; set; }
}
/// <summary>
/// 用于Mqtt Json传输上传/下载配置信息
/// </summary>
public class MqttDBUploadRpcResult
{
/// <summary>
/// 采集设备
/// </summary>
public List<CollectDevice> CollectDevices { get; set; } = new();
/// <summary>
/// 上传设备
/// </summary>
public List<UploadDevice> UploadDevices { get; set; } = new();
/// <summary>
/// 变量
/// </summary>
public List<DeviceVariable> DeviceVariables { get; set; } = new();
}
/// <summary>
/// 用于Mqtt Json传输上传/下载配置信息
/// </summary>
public class MqttDBDownRpc
{
/// <summary>
/// 采集设备
/// </summary>
public byte[] CollectDevices { get; set; }
/// <summary>
/// 上传设备
/// </summary>
public byte[] UploadDevices { get; set; }
/// <summary>
/// 变量
/// </summary>
public byte[] DeviceVariables { get; set; }
/// <summary>
/// true=>删除全部后增加
/// </summary>
[Description("是否删除原采集设备表")]
public bool IsCollectDevicesFullUp { get; set; }
/// <summary>
/// true=>删除全部后增加
/// </summary>
[Description("是否删除原上传设备表")]
public bool IsUploadDevicesFullUp { get; set; }
/// <summary>
/// true=>删除全部后增加
/// </summary>
[Description("是否删除原变量表")]
public bool IsDeviceVariablesFullUp { get; set; }
/// <summary>
/// 是否立即重启,使配置生效
/// </summary>
[Description("是否重启子网关线程")]
public bool IsRestart { get; set; }
}
/// <summary>
/// MqttRpc传入
/// </summary>
public class ManageMqttRpcFrom
{
/// <summary>
/// 标识
/// </summary>
public string GatewayId { get; set; }
/// <summary>
/// 标识
/// </summary>
public string RpcId { get; set; }
/// <summary>
/// "WriteInfos":{"test":"1"}
/// </summary>
public Dictionary<string, string> WriteInfos { get; set; } = new();
}
/// <summary>
/// MqttRpc输出
/// </summary>
public class ManageMqttRpcResult
{
/// <summary>
/// 标识
/// </summary>
public string GatewayId { get; set; }
/// <summary>
/// 标识
/// </summary>
public string RpcId { get; set; }
/// <summary>
/// 消息
/// </summary>
public Dictionary<string, OperResult> Message { get; set; } = new();
/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
}

View File

@@ -0,0 +1,800 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Logging.Extensions;
using Mapster;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
using System.Collections.Concurrent;
using System.Net;
using System.Text;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// ManageGatewayWorker
/// </summary>
public class ManageGatewayWorker : BackgroundService
{
private readonly ILogger _clientLogger;
private readonly ILogger _logger;
private readonly ILogger _manageLogger;
/// <summary>
/// 全部重启锁
/// </summary>
private readonly EasyLock restartLock = new();
private IMqttClient _mqttClient;
private MqttServer _mqttServer;
private MqttClientSubscribeOptions _mqttSubscribeOptions;
/// <inheritdoc cref="ManageGatewayWorker"/>
public ManageGatewayWorker(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger("ManageGatewayWorker");
_manageLogger = loggerFactory.CreateLogger("管理网关(mqttBroker)");
_clientLogger = loggerFactory.CreateLogger("子网关(mqttClient)");
}
/// <summary>
/// 服务状态
/// </summary>
public OperResult ClientStatuString { get; set; } = new OperResult("初始化");
/// <summary>
/// 服务状态
/// </summary>
public OperResult ManageStatuString { get; set; } = new OperResult("初始化");
#region worker服务
/// <inheritdoc/>
public override async Task StartAsync(CancellationToken token)
{
_logger?.LogInformation("ManageGatewayWorker启动");
await RestartAsync();
await base.StartAsync(token);
}
/// <inheritdoc/>
public override async Task StopAsync(CancellationToken token)
{
_logger?.LogInformation("ManageGatewayWorker停止");
await StopAsync();
await base.StopAsync(token);
}
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
if (_mqttClient != null)
{
//持续重连
var result = await TryMqttClientAsync(stoppingToken);
if (result.IsSuccess)
{
_clientLogger.LogDebug($"连接正常:{result.Message}");
ClientStatuString.ErrorCode = 0;
ClientStatuString.Message = "连接正常:" + result.Message;
}
else
{
if (ClientStatuString.IsSuccess)
{
_clientLogger.LogWarning($"连接错误:{result.Message}");
}
ClientStatuString.ErrorCode = 999;
ClientStatuString.Message = $"连接错误:{result.Message}";
}
}
await Task.Delay(10000, stoppingToken);
}
catch (TaskCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_logger.LogError(ex, ToString());
}
}
}
#endregion
#region public
/// <summary>
/// 获取子网关的配置信息
/// </summary>
/// <returns></returns>
public async Task<OperResult<MqttDBUploadRpcResult>> GetClientGatewayDBAsync(string gatewayId, int timeOut = 3000, CancellationToken token = default)
{
try
{
var buffer = Encoding.UTF8.GetBytes(string.Empty);
var response = await RpcDataExecuteAsync(gatewayId, ClientGatewayConfig.DBUploadTopic, buffer, timeOut, MqttQualityOfServiceLevel.AtMostOnce, token);
var data = Encoding.UTF8.GetString(response).FromJsonString<MqttDBUploadRpcResult>();
return OperResult.CreateSuccessResult(data);
}
catch (Exception ex)
{
return new OperResult<MqttDBUploadRpcResult>(ex);
}
}
/// <summary>
/// 重启
/// </summary>
/// <returns></returns>
public async Task RestartAsync()
{
await StopAsync();
await StartAsync();
}
/// <summary>
/// 下载配置信息到子网关
/// </summary>
/// <returns></returns>
public async Task<OperResult> SetClientGatewayDBAsync(string gatewayId, MqttDBDownRpc mqttDBRpc, int timeOut = 3000, CancellationToken token = default)
{
try
{
var buffer = Encoding.UTF8.GetBytes(mqttDBRpc?.ToJsonString() ?? string.Empty);
var response = await RpcDataExecuteAsync(gatewayId, ClientGatewayConfig.DBDownTopic, buffer, timeOut, MqttQualityOfServiceLevel.AtMostOnce, token);
var data = Encoding.UTF8.GetString(response).FromJsonString<OperResult>();
return data;
}
catch (Exception ex)
{
return new OperResult(ex);
}
}
/// <summary>
/// 写入变量到子网关
/// </summary>
/// <returns></returns>
public async Task<OperResult<ManageMqttRpcResult>> WriteVariableAsync(ManageMqttRpcFrom manageMqttRpcFrom, int timeOut = 3000, CancellationToken token = default)
{
try
{
var payload = Encoding.UTF8.GetBytes(manageMqttRpcFrom?.ToJsonString() ?? string.Empty);
var requestTopic = ManageGatewayConfig.WriteRpcTopic;
var responseTopic = GetRpcReturnTopic(ManageGatewayConfig.WriteRpcTopic);
var key = GetRpcReturnIdTopic(manageMqttRpcFrom.GatewayId, requestTopic, manageMqttRpcFrom.RpcId);
ManageMqttRpcResult result = await RpcWriteExecuteAsync(timeOut, payload, requestTopic, key, token);
return OperResult.CreateSuccessResult(result);
}
catch (Exception ex)
{
return new OperResult<ManageMqttRpcResult>(ex);
}
}
/// <summary>
/// 获取子网关列表
/// </summary>
/// <returns></returns>
public async Task<List<MqttClientStatus>> GetClientGatewayAsync()
{
if (_mqttServer != null)
{
var data = await _mqttServer.GetClientsAsync();
return data.ToList();
}
else
{
return new List<MqttClientStatus>();
}
}
#endregion
#region RPC实现
readonly ConcurrentDictionary<string, WaitDataAsync<byte[]>> _waitingCalls = new();
readonly ConcurrentDictionary<string, WaitDataAsync<ManageMqttRpcResult>> _writerRpcResultWaitingCalls = new();
private readonly EasyLock clientLock = new();
private async Task<ManageMqttRpcResult> RpcWriteExecuteAsync(int timeOut, byte[] payload, string requestTopic, string key, CancellationToken token)
{
try
{
using WaitDataAsync<ManageMqttRpcResult> waitDataAsync = new();
if (!_writerRpcResultWaitingCalls.TryAdd(key, waitDataAsync))
{
throw new InvalidOperationException();
}
waitDataAsync.SetCancellationToken(token);
//请求子网关的数据
var message = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).Build();
await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message), token);
var result = await waitDataAsync.WaitAsync(timeOut);
switch (result)
{
case WaitDataStatus.SetRunning:
return waitDataAsync.WaitResult;
case WaitDataStatus.Overtime:
throw new TimeoutException();
case WaitDataStatus.Canceled:
{
throw new Exception("等待已终止。可能是客户端已掉线,或者被注销。");
}
case WaitDataStatus.Default:
case WaitDataStatus.Disposed:
default:
throw new Exception("未知错误");
}
}
finally
{
_writerRpcResultWaitingCalls.Remove(key);
}
}
/// <summary>
/// RPC请求子网关并返回需要传入子网关ID作为Topic参数一部分
/// </summary>
/// <returns></returns>
private async Task<byte[]> RpcDataExecuteAsync(string gatewayId, string topic, byte[] payload, int timeOut, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken token = default)
{
var responseTopic = GetRpcReturnTopic(gatewayId, topic);
var requestTopic = GetRpcTopic(gatewayId, topic);
try
{
using WaitDataAsync<byte[]> waitDataAsync = new();
if (!_waitingCalls.TryAdd(responseTopic, waitDataAsync))
{
throw new InvalidOperationException();
}
waitDataAsync.SetCancellationToken(token);
//请求子网关的数据
var message = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).Build();
await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message), token);
var result = await waitDataAsync.WaitAsync(timeOut);
switch (result)
{
case WaitDataStatus.SetRunning:
return waitDataAsync.WaitResult;
case WaitDataStatus.Overtime:
throw new TimeoutException();
case WaitDataStatus.Canceled:
{
throw new Exception("等待已终止。可能是客户端已掉线,或者被注销。");
}
case WaitDataStatus.Default:
case WaitDataStatus.Disposed:
default:
throw new Exception("未知错误");
}
}
finally
{
_waitingCalls.Remove(responseTopic);
}
}
#endregion
#region
internal async Task StartAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
await InitAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "启动错误");
}
finally
{
restartLock.Release();
}
}
internal async Task StopAsync()
{
try
{
//重启操作在未完全之前直接取消
if (restartLock.IsWaitting)
{
return;
}
await restartLock.WaitAsync();
_mqttClient?.SafeDispose();
_mqttServer?.SafeDispose();
_mqttClient = null;
_mqttServer = null;
}
catch (Exception ex)
{
_logger.LogError(ex, "停止错误");
}
finally
{
restartLock.Release();
}
}
/// <summary>
/// 初始化
/// </summary>
private async Task InitAsync()
{
try
{
ManageGatewayConfig = App.GetConfig<ManageGatewayConfig>("ManageGatewayConfig");
if (ManageGatewayConfig?.Enable != true)
{
ManageStatuString = new OperResult($"已退出:不启用管理功能");
_manageLogger.LogWarning("已退出:不启用管理功能");
}
else
{
var mqttFactory = new MqttFactory(new MqttNetLogger(_manageLogger));
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder()
.WithDefaultEndpointBoundIPAddress(string.IsNullOrEmpty(ManageGatewayConfig.MqttBrokerIP) ? null : IPAddress.Parse(ManageGatewayConfig.MqttBrokerIP))
.WithDefaultEndpointPort(ManageGatewayConfig.MqttBrokerPort)
.WithDefaultEndpoint()
.Build();
_mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions);
if (_mqttServer != null)
{
_mqttServer.ValidatingConnectionAsync += MqttServer_ValidatingConnectionAsync;//认证
_mqttServer.InterceptingPublishAsync += MqttServer_InterceptingPublishAsync;//消息
await _mqttServer.StartAsync();
}
ManageStatuString = OperResult.CreateSuccessResult();
}
}
catch (Exception ex)
{
_manageLogger.LogError(ex, "初始化失败");
ManageStatuString = new($"初始化失败-{ex.Message}");
}
try
{
ClientGatewayConfig = App.GetConfig<ClientGatewayConfig>("ClientGatewayConfig");
if (ClientGatewayConfig?.Enable != true)
{
ClientStatuString = new OperResult($"已退出:不启用子网关功能");
_clientLogger.LogWarning("已退出:不启用子网关功能");
}
else
{
var mqttFactory = new MqttFactory(new MqttNetLogger(_clientLogger));
_mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithCredentials(ClientGatewayConfig.UserName, ClientGatewayConfig.Password)//账密
.WithTcpServer(ClientGatewayConfig.MqttBrokerIP, ClientGatewayConfig.MqttBrokerPort)//服务器
.WithClientId(ClientGatewayConfig.GatewayId)
.WithCleanSession(true)
.WithKeepAlivePeriod(TimeSpan.FromSeconds(120.0))
.WithoutThrowOnNonSuccessfulConnectResponse()
.Build();
_mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic(ClientGatewayConfig.WriteRpcTopic);
f.WithAtMostOnceQoS();
})
.WithTopicFilter(
f =>
{
f.WithTopic(GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBDownTopic));
f.WithAtMostOnceQoS();
})
.WithTopicFilter(
f =>
{
f.WithTopic(GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBUploadTopic));
f.WithAtMostOnceQoS();
})
.Build();
_mqttClient = mqttFactory.CreateMqttClient();
_mqttClient.ConnectedAsync += MqttClient_ConnectedAsync;
_mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync;
await TryMqttClientAsync(CancellationToken.None);
}
}
catch (Exception ex)
{
_clientLogger.LogError(ex, "初始化失败");
}
}
/// <summary>
/// ClientGatewayConfig
/// </summary>
public ClientGatewayConfig ClientGatewayConfig;
/// <summary>
/// ManageGatewayConfig
/// </summary>
public ManageGatewayConfig ManageGatewayConfig;
private MqttClientOptions _mqttClientOptions;
RpcSingletonService _rpcCore;
private async Task DBDownTopicMethod(MqttApplicationMessageReceivedEventArgs args)
{
var mqttDBRpc = args.ApplicationMessage.PayloadSegment.Count > 0 ? Encoding.UTF8.GetString(args.ApplicationMessage.PayloadSegment).FromJsonString<MqttDBDownRpc>() : null;
if (mqttDBRpc != null)
{
OperResult result = new();
var collectDeviceService = App.GetService<CollectDeviceService>();
var variableService = App.GetService<VariableService>();
var uploadDeviceService = App.GetService<UploadDeviceService>();
collectDeviceService.Context = variableService.Context = uploadDeviceService.Context;
var itenant = collectDeviceService.Context.AsTenant();
//事务
var dbResult = await itenant.UseTranAsync(async () =>
{
if (mqttDBRpc.IsCollectDevicesFullUp)
{
await collectDeviceService.AsDeleteable().ExecuteCommandAsync();
}
var collectDevices = new List<CollectDevice>();
if (mqttDBRpc.CollectDevices != null && mqttDBRpc.CollectDevices.Length > 0)
{
using MemoryStream stream = new(mqttDBRpc.CollectDevices);
var previewResult = await collectDeviceService.PreviewAsync(stream);
if (previewResult.FirstOrDefault().Value.HasError)
{
throw new(previewResult.Select(a => a.Value.Results.Where(a => !a.isSuccess).ToList()).ToList().ToJsonString());
}
foreach (var item in previewResult)
{
if (item.Key == ExportHelpers.CollectDeviceSheetName)
{
var collectDeviceImports = ((ImportPreviewOutput<CollectDevice>)item.Value).Data;
collectDevices = collectDeviceImports.Values.Adapt<List<CollectDevice>>();
break;
}
}
await collectDeviceService.ImportAsync(previewResult);
}
if (mqttDBRpc.IsUploadDevicesFullUp)
{
await uploadDeviceService.AsDeleteable().ExecuteCommandAsync();
}
var uploadDevices = new List<UploadDevice>();
if (mqttDBRpc.UploadDevices != null && mqttDBRpc.UploadDevices.Length > 0)
{
using MemoryStream stream1 = new(mqttDBRpc.UploadDevices);
var previewResult1 = await uploadDeviceService.PreviewAsync(stream1);
if (previewResult1.FirstOrDefault().Value.HasError)
{
throw new(previewResult1.Select(a => a.Value.Results.Where(a => !a.isSuccess).ToList()).ToList().ToJsonString());
}
foreach (var item in previewResult1)
{
if (item.Key == ExportHelpers.UploadDeviceSheetName)
{
var uploadDeviceImports = ((ImportPreviewOutput<UploadDevice>)item.Value).Data;
uploadDevices = uploadDeviceImports.Values.Adapt<List<UploadDevice>>();
break;
}
}
await uploadDeviceService.ImportAsync(previewResult1);
}
if (mqttDBRpc.IsDeviceVariablesFullUp)
{
await variableService.AsDeleteable().ExecuteCommandAsync();
}
if (mqttDBRpc.DeviceVariables != null && mqttDBRpc.DeviceVariables.Length > 0)
{
using MemoryStream stream2 = new(mqttDBRpc.DeviceVariables);
var previewResult2 = await variableService.PreviewAsync(stream2, collectDevices, uploadDevices);
if (previewResult2.FirstOrDefault().Value.HasError)
{
throw new(previewResult2.Select(a => a.Value.Results.Where(a => !a.isSuccess).ToList()).ToList().ToJsonString());
}
await variableService.ImportAsync(previewResult2);
}
});
Cache.SysMemoryCache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除
Cache.SysMemoryCache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除
if (dbResult.IsSuccess)//如果成功了
{
_clientLogger.LogInformation("子网关接收配置,并保存至数据库-执行成功");
result = OperResult.CreateSuccessResult();
if (mqttDBRpc.IsRestart)
{
_clientLogger.LogInformation("子网关接收配置,并重启");
await BackgroundServiceUtil.GetBackgroundService<CollectDeviceWorker>().RestartDeviceThreadAsync();
}
}
else
{
//写日志
result.Message = dbResult.ErrorMessage;
}
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(GetRpcReturnTopic(args.ApplicationMessage.Topic))
.WithPayload(result.ToJsonString()).Build();
if (_mqttClient.IsConnected)
await _mqttClient.PublishAsync(variableMessage);
}
}
private async Task DBUploadTopicMethod(MqttApplicationMessageReceivedEventArgs args)
{
MqttDBUploadRpcResult result = new();
var collectDeviceService = App.GetService<CollectDeviceService>();
var variableService = App.GetService<VariableService>();
var uploadDeviceService = App.GetService<UploadDeviceService>();
result.CollectDevices = collectDeviceService.GetCacheList(false);
result.DeviceVariables = await variableService.GetListAsync();
result.UploadDevices = uploadDeviceService.GetCacheList(false);
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(GetRpcReturnTopic(args.ApplicationMessage.Topic))
.WithPayload(result.ToJsonString()).Build();
if (_mqttClient.IsConnected)
await _mqttClient.PublishAsync(variableMessage);
}
private string GetRpcReturnIdTopic(string gatewayId, string topic, string rpcId)
{
var responseTopic = $"{gatewayId}/{topic}/rpc/Return/rpcId";
return responseTopic;
}
private string GetRpcReturnTopic(string gatewayId, string topic)
{
var responseTopic = $"{gatewayId}/{topic}/rpc/Return";
return responseTopic;
}
private string GetRpcReturnTopic(string requestTopic)
{
var responseTopic = $"{requestTopic}/Return";
return responseTopic;
}
private string GetRpcTopic(string gatewayId, string topic)
{
var requestTopic = $"{gatewayId}/{topic}/rpc";
return requestTopic;
}
private async Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs args)
{
if (args.ApplicationMessage.Topic == GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBUploadTopic))
{
_clientLogger.LogInformation("子网关配置上传");
await DBUploadTopicMethod(args);
return;
}
if (args.ApplicationMessage.Topic == GetRpcTopic(ClientGatewayConfig.GatewayId, ClientGatewayConfig.DBDownTopic))
{
_clientLogger.LogInformation("子网关接收配置,并保存至数据库");
await DBDownTopicMethod(args);
return;
}
if (args.ApplicationMessage.Topic == ClientGatewayConfig.WriteRpcTopic)
{
await WriteRpcTopicMethod(args);
return;
}
}
private async Task MqttClient_ConnectedAsync(MqttClientConnectedEventArgs args)
{
var subResult = await _mqttClient.SubscribeAsync(_mqttSubscribeOptions);
if (subResult.Items.Any(a => a.ResultCode > (MqttClientSubscribeResultCode)10))
{
_clientLogger?.LogWarning("订阅失败-" + subResult.Items
.Where(a => a.ResultCode > (MqttClientSubscribeResultCode)10)
.Select(a =>
new
{
Topic = a.TopicFilter.Topic,
ResultCode = a.ResultCode.ToString()
}
)
.ToJsonString()
);
}
}
private Task MqttServer_InterceptingPublishAsync(InterceptingPublishEventArgs eventArgs)
{
if (eventArgs.ApplicationMessage.Topic == GetRpcReturnTopic(ManageGatewayConfig.WriteRpcTopic))
{
if (!_writerRpcResultWaitingCalls.IsEmpty)
{
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
var manageMqttRpcResult = Encoding.UTF8.GetString(payloadBuffer).FromJsonString<ManageMqttRpcResult>();
var key = GetRpcReturnIdTopic(manageMqttRpcResult.GatewayId, ManageGatewayConfig.WriteRpcTopic, manageMqttRpcResult.RpcId);
if (!_writerRpcResultWaitingCalls.TryRemove(key, out var writeRpcResultAsync))
{
return CompletedTask.Instance;
}
writeRpcResultAsync.Set(manageMqttRpcResult);
}
}
else
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.Set(payloadBuffer);
}
return CompletedTask.Instance;
}
private Task MqttServer_ValidatingConnectionAsync(ValidatingConnectionEventArgs arg)
{
if (ManageGatewayConfig.UserName != arg.UserName)
{
arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
}
if (ManageGatewayConfig.Password != arg.Password)
{
arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
}
_manageLogger?.LogInformation(ToString() + "-" + arg.ClientId + "-客户端已连接成功");
return CompletedTask.Instance;
}
private async Task<OperResult> TryMqttClientAsync(CancellationToken token)
{
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
return await Cilent();
async Task<OperResult> Cilent()
{
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
try
{
await clientLock.WaitAsync();
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(5000));
using CancellationTokenSource StoppingToken = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutToken.Token);
if (_mqttClient?.IsConnected == true)
return OperResult.CreateSuccessResult();
if (_mqttClient == null)
{
return new OperResult("未初始化");
}
var result = await _mqttClient?.ConnectAsync(_mqttClientOptions, StoppingToken.Token);
if (result.ResultCode == MqttClientConnectResultCode.Success)
{
return OperResult.CreateSuccessResult();
}
else
{
return new OperResult(result.ReasonString);
}
}
catch (Exception ex)
{
return new OperResult(ex);
}
finally
{
clientLock.Release();
}
}
}
private async Task WriteRpcTopicMethod(MqttApplicationMessageReceivedEventArgs args)
{
var manageMqttRpcFrom = args.ApplicationMessage.PayloadSegment.Count > 0 ? Encoding.UTF8.GetString(args.ApplicationMessage.PayloadSegment).FromJsonString<ManageMqttRpcFrom>() : null;
if (manageMqttRpcFrom != null && manageMqttRpcFrom.GatewayId == ClientGatewayConfig.GatewayId)
{
ManageMqttRpcResult mqttRpcResult = new() { RpcId = manageMqttRpcFrom.RpcId, GatewayId = manageMqttRpcFrom.GatewayId };
_rpcCore ??= App.GetService<RpcSingletonService>();
var result = await _rpcCore.InvokeDeviceMethodAsync("子网关RPC" + "-" + args.ClientId,
manageMqttRpcFrom.WriteInfos.Where(
a => !mqttRpcResult.Message.Any(b => b.Key == a.Key)).ToDictionary(a => a.Key, a => a.Value));
mqttRpcResult.Message.AddRange(result);
mqttRpcResult.Success = !mqttRpcResult.Message.Any(a => !a.Value.IsSuccess);
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(GetRpcReturnTopic(args.ApplicationMessage.Topic))
.WithPayload(mqttRpcResult.ToJsonString()).Build();
if (_mqttClient.IsConnected)
await _mqttClient.PublishAsync(variableMessage);
}
}
#endregion
}

View File

@@ -0,0 +1,51 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Microsoft.Extensions.Logging;
using MQTTnet.Diagnostics;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace ThingsGateway.Gateway.Application;
internal class MqttNetLogger : IMqttNetLogger
{
readonly ILogger LogMessage;
public MqttNetLogger(ILogger logger)
{
LogMessage = logger;
}
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
{
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
LogMessage?.Log(LogLevel.Trace, source, message != null ? (parameters != null ? message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty : message) : string.Empty, exception);
break;
case MqttNetLogLevel.Info:
LogMessage?.Log(LogLevel.Information, source, message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty, exception);
break;
case MqttNetLogLevel.Warning:
LogMessage?.Log(LogLevel.Warning, source, message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty, exception);
break;
case MqttNetLogLevel.Error:
LogMessage?.Log(LogLevel.Warning, source, message != null ? (parameters != null ? string.Format(message, parameters) : message) : string.Empty, exception);
break;
}
}
}

View File

@@ -60,7 +60,6 @@ public partial class DeviceStatusPage : IDisposable
MainLayout MainLayout { get; set; }
MemoryVariableWorker MemoryVariableWorker { get; set; }
StringNumber Panel { get; set; }
UploadDeviceWorker UploadDeviceHostService { get; set; }
StringNumber Uppanel { get; set; }

View File

@@ -0,0 +1,205 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/gatewayconfig/manage"
@namespace ThingsGateway.Gateway.Blazor
@using System.Linq.Expressions;
@using BlazorComponent;
@using MQTTnet.Server;
@using Mapster;
@using Masa.Blazor.Presets;
@using System.IO;
@using Masa.Blazor;
@using Microsoft.AspNetCore.Authorization;
@using ThingsGateway.Core;
@using ThingsGateway.Components;
@using ThingsGateway.Admin.Blazor;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Gateway.Application;
@attribute [Authorize]
@inherits BaseComponentBase
@inject UserResoures UserResoures
@inject NavigationManager NavigationManager
@layout MainLayout
<MTabs @bind-Value="tab">
<MTab Value="1">
管理服务
</MTab>
<MTab Value="2">
子网关服务
</MTab>
</MTabs>
<MTabsItems Value="tab">
<MTabItem Value="1">
@if (tab == 1)
{
<MSheet Style="overflow:auto" Class="pa-2">
<MSubheader>
@($"管理服务信息-{ManageGatewayWorker.ManageStatuString.Message}")
</MSubheader>
@{
var config = ManageGatewayWorker.ManageGatewayConfig;
}
@if (config != null)
{
<MDescriptions Title="管理服务配置信息" Bordered="true">
<MDescriptionsItem Label=@config.Description(a=>a.Enable)>@config.Enable</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerIP)>@config.MqttBrokerIP</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerPort)>@config.MqttBrokerPort</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.UserName)>@config.UserName</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.Password)>@config.Password</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBDownTopic)>@config.DBDownTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBUploadTopic)>@config.DBUploadTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.WriteRpcTopic)>@config.WriteRpcTopic</MDescriptionsItem>
</MDescriptions>
}
<MCard Flat Class="ma-0" Style="min-height:1000px">
<div class="m-descriptions-header__title my-2">
当前服务下的子网关
</div>
<MRow NoGutters>
<MCol Md=3>
<MTreeview Dense TItem="MqttClientStatus"
TKey="MqttClientStatus" OpenOnClick @bind-Active=CurClients
Items="MqttClientStatuses" ItemText=@(r=>r.Id) ItemChildren="r=> null"
Activatable ItemKey=@(r=>r)>
<LabelContent>
<span title=@context.Item.Id>
@(context.Item.Id + "-" + context.Item.Endpoint)
</span>
</LabelContent>
</MTreeview>
</MCol>
<MCol Md=9>
@if (CurClients != null && CurClients.Count > 0)
{
var CurClient = CurClients.FirstOrDefault();
<MCard Flat Class="ml-4">
<MDescriptions Title="当前选择的子网关" Bordered="true">
<MDescriptionsItem Label=@CurClient.Description(a=>a.Id)>@CurClient.Id</MDescriptionsItem>
<MDescriptionsItem Label=@CurClient.Description(a=>a.Endpoint)>@CurClient.Endpoint</MDescriptionsItem>
</MDescriptions>
<MDivider></MDivider>
<MRow>
<MCol Cols="12" Md="12">
<div class="m-descriptions-header__title my-2">
导出子网关配置信息
</div>
</MCol>
<MCol Cols="12" Md="12">
<MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="ma-2"
OnClick=@(()=>DBUpload(CurClient))>
导出
</MButton>
</MCol>
</MRow>
<MDivider></MDivider>
<MRow>
<MCol Cols="12" Md="12">
<div class="m-descriptions-header__title my-2">
下发子网关配置信息
</div>
</MCol>
<MCol Cols="12" Md="12">
<MFileInput Label="采集设备Excel" @bind-Value="_importCollectDevicesFile" Style="width:60%;" ShowSize></MFileInput>
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsCollectDevicesFullUp)) @bind-Value=@IsCollectDevicesFullUp />
</MCol>
<MCol Cols="12" Md="12">
<MFileInput Label="上传设备Excel" @bind-Value="_importUploadDevicesFile" Style="width:60%;" ShowSize></MFileInput>
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsUploadDevicesFullUp)) @bind-Value=@IsUploadDevicesFullUp />
</MCol>
<MCol Cols="12" Md="12">
<MFileInput Label="变量Excel" @bind-Value="_importDeviceVariablesFile" Style="width:60%;" ShowSize></MFileInput>
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsDeviceVariablesFullUp)) @bind-Value=@IsDeviceVariablesFullUp />
</MCol>
<MCol Cols="12" Md="12">
<MSwitch Label=@typeof(MqttDBDownRpc).GetDescription(nameof(MqttDBDownRpc.IsRestart)) @bind-Value=@IsRestart />
</MCol>
<MCol Cols="12" Md="12">
<MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="ma-2"
OnClick=@(()=>DBDown(CurClient))>
下发
</MButton>
</MCol>
</MRow>
<MDivider></MDivider>
</MCard>
}
</MCol>
</MRow>
</MCard>
</MSheet>
}
</MTabItem>
<MTabItem Value="2">
@if (tab == 2)
{
<MSheet Style="overflow:auto" Class="pa-2">
<MSubheader>
@($"子网关服务信息-{ManageGatewayWorker.ClientStatuString.Message}")
</MSubheader>
@{
var config = ManageGatewayWorker.ClientGatewayConfig;
}
@if (config != null)
{
<MDescriptions Title="子网关服务配置信息" Bordered="true">
<MDescriptionsItem Label=@config.Description(a=>a.Enable)>@config.Enable</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.GatewayId)>@config.GatewayId</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerIP)>@config.MqttBrokerIP</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.MqttBrokerPort)>@config.MqttBrokerPort</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.UserName)>@config.UserName</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.Password)>@config.Password</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBDownTopic)>@config.DBDownTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.DBUploadTopic)>@config.DBUploadTopic</MDescriptionsItem>
<MDescriptionsItem Label=@config.Description(a=>a.WriteRpcTopic)>@config.WriteRpcTopic</MDescriptionsItem>
</MDescriptions>
}
</MSheet>
}
</MTabItem>
</MTabsItems>

View File

@@ -0,0 +1,202 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using BlazorComponent;
using Furion;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
using MQTTnet.Server;
namespace ThingsGateway.Gateway.Blazor;
/// <summary>
/// ManageGatewayPage
/// </summary>
public partial class ManageGatewayPage
{
readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(3));
IBrowserFile _importCollectDevicesFile;
IBrowserFile _importDeviceVariablesFile;
IBrowserFile _importUploadDevicesFile;
private bool IsCollectDevicesFullUp;
private bool IsDeviceVariablesFullUp;
private bool isDownExport;
private bool IsRestart;
StringNumber tab;
private bool IsUploadDevicesFullUp;
private IJSObjectReference JSObjectReference;
/// <inheritdoc/>
[Inject]
protected IJSRuntime JSRuntime { get; set; }
List<MqttClientStatus> CurClients { get; set; }
ManageGatewayWorker ManageGatewayWorker { get; set; }
List<MqttClientStatus> MqttClientStatuses { get; set; } = new();
List<StringNumber> Panel { get; set; } = new();
/// <inheritdoc/>
public override void Dispose()
{
_periodicTimer?.Dispose();
base.Dispose();
}
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
ManageGatewayWorker = BackgroundServiceUtil.GetBackgroundService<ManageGatewayWorker>();
Panel.Add("2");
_ = RunTimerAsync();
base.OnInitialized();
}
/// <summary>
/// 下发子网关配置
/// </summary>
/// <returns></returns>
private async Task DBDown(MqttClientStatus mqttClientStatus)
{
MqttDBDownRpc rpc = new()
{
IsCollectDevicesFullUp = IsCollectDevicesFullUp,
IsDeviceVariablesFullUp = IsDeviceVariablesFullUp,
IsUploadDevicesFullUp = IsUploadDevicesFullUp,
IsRestart = IsRestart
};
if (_importCollectDevicesFile != null)
{
using var fs1 = new MemoryStream();
using var stream1 = _importCollectDevicesFile.OpenReadStream(512000000);
await stream1.CopyToAsync(fs1);
rpc.CollectDevices = fs1.ToArray();
}
if (_importUploadDevicesFile != null)
{
using var fs2 = new MemoryStream();
using var stream2 = _importUploadDevicesFile.OpenReadStream(512000000);
await stream2.CopyToAsync(fs2);
rpc.UploadDevices = fs2.ToArray();
}
if (_importDeviceVariablesFile != null)
{
using var fs3 = new MemoryStream();
using var stream3 = _importDeviceVariablesFile.OpenReadStream(512000000);
await stream3.CopyToAsync(fs3);
rpc.DeviceVariables = fs3.ToArray();
}
var data = await ManageGatewayWorker.SetClientGatewayDBAsync(mqttClientStatus.Id, rpc);
if (data.IsSuccess)
{
await PopupService.EnqueueSnackbarAsync("下发成功", AlertTypes.Success);
}
else
{
await PopupService.EnqueueSnackbarAsync(data.Message, AlertTypes.Error);
}
}
/// <summary>
/// 获取子网关配置导出excel
/// </summary>
/// <param name="mqttClientStatus"></param>
/// <returns></returns>
private async Task DBUpload(MqttClientStatus mqttClientStatus)
{
var data = await ManageGatewayWorker.GetClientGatewayDBAsync(mqttClientStatus.Id);
if (data.IsSuccess)
{
isDownExport = true;
await InvokeAsync(StateHasChanged);
if (data.Content.CollectDevices.Count > 0)
{
using var devices = await App.GetService<CollectDeviceService>().ExportFileAsync(data.Content.CollectDevices);
using var streamRef = new DotNetStreamReference(stream: devices);
JSObjectReference ??= await JSRuntime.LoadModuleAsync("js/downloadFileFromStream");
await JSObjectReference.InvokeVoidAsync("downloadFileFromStream", $"子网关{mqttClientStatus.Id}采集设备导出{DateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
else
{
await PopupService.EnqueueSnackbarAsync("无采集设备", AlertTypes.None);
}
if (data.Content.UploadDevices.Count > 0)
{
using var devices = await App.GetService<UploadDeviceService>().ExportFileAsync(data.Content.UploadDevices);
using var streamRef = new DotNetStreamReference(stream: devices);
JSObjectReference ??= await JSRuntime.LoadModuleAsync("js/downloadFileFromStream");
await JSObjectReference.InvokeVoidAsync("downloadFileFromStream", $"子网关{mqttClientStatus.Id}上传设备导出{DateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
else
{
await PopupService.EnqueueSnackbarAsync("无上传设备", AlertTypes.None);
}
if (data.Content.DeviceVariables.Count > 0)
{
using var devices = await App.GetService<VariableService>().ExportFileAsync(data.Content.DeviceVariables);
using var streamRef = new DotNetStreamReference(stream: devices);
JSObjectReference ??= await JSRuntime.LoadModuleAsync("js/downloadFileFromStream");
await JSObjectReference.InvokeVoidAsync("downloadFileFromStream", $"子网关{mqttClientStatus.Id}变量导出{DateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef);
}
else
{
await PopupService.EnqueueSnackbarAsync("无采集变量", AlertTypes.None);
}
await PopupService.EnqueueSnackbarAsync("上传成功", AlertTypes.Success);
}
else
{
await PopupService.EnqueueSnackbarAsync(data.Message, AlertTypes.Error);
}
isDownExport = false;
}
private async Task RefreshAsync()
{
MqttClientStatuses = await ManageGatewayWorker.GetClientGatewayAsync();
}
private async Task RunTimerAsync()
{
await RefreshAsync();
while (await _periodicTimer.WaitForNextTickAsync())
{
try
{
await RefreshAsync();
await InvokeAsync(StateHasChanged);
}
catch
{
}
}
}
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/net6/schemas/v3/furion-schema.json",
"ManageGatewayConfig": {
"Enable": true, //是否启用管理服务
"MqttBrokerIP": "127.0.0.1", //管理服务IP
"MqttBrokerPort": 7300, //管理服务端口
"UserName": "admin", //管理服务用户名
"Password": "111111", //管理服务端口
"DBDownTopic": "DBDownTopic", //下发网关配置的主题
"DBUploadTopic": "DBUploadTopic", //子网关上传配置信息的主题
"WriteRpcTopic": "WriteRpcTopic"
},
"ClientGatewayConfig": {
"GatewayId": "GatewayId", //子网关ID需要唯一也用于MqttClientID
"Enable": false, //是否连接管理服务
"MqttBrokerIP": "127.0.0.1", //管理服务IP
"MqttBrokerPort": 7300, //管理服务端口
"UserName": "admin", //管理服务用户名
"Password": "111111", //管理服务端口
"DBDownTopic": "DBDownTopic", //下发网关配置的主题
"DBUploadTopic": "DBUploadTopic", //子网关上传配置信息的主题
"WriteRpcTopic": "WriteRpcTopic"
}
}

View File

@@ -1,10 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<None Remove="Configuration\UpgradeConfig.json" />
</ItemGroup>
<ItemGroup>
<Content Include="Configuration\App.json">
@@ -16,6 +12,9 @@
<Content Include="Configuration\JWT.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Configuration\ManageGatewayConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Configuration\Swagger.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>