Compare commits

...

20 Commits

Author SHA1 Message Date
Diego
8e938f18be 去除不必要的控制台日志输出 2025-05-28 16:52:51 +08:00
Diego
ab1b364c54 fix: 同步插件反写空错误 2025-05-28 12:30:16 +08:00
Diego
5ec65b2fb0 10.6.29 2025-05-28 10:47:31 +08:00
Diego
926eced724 10.6.28 2025-05-27 17:20:05 +08:00
Diego
f7f8802272 2025-05-27 13:19:39 +08:00
Diego
c6910dff02 update src/Gateway/ThingsGateway.Gateway.Application/ThingsGateway.Gateway.Application.csproj.
Signed-off-by: Diego <2248356998@qq.com>
2025-05-27 01:37:29 +00:00
Diego
ad299d0dbb 2025-05-27 08:52:06 +08:00
2248356998 qq.com
8b124d1050 日志统计查询性能增强 2025-05-27 00:03:30 +08:00
Diego
ff41080dbd 更新授权类 2025-05-26 19:51:21 +08:00
Diego
0e28606e3d 10.6.23 2025-05-26 18:43:42 +08:00
Diego
6a025ceee5 序列化配置增加nan的情况 2025-05-26 17:41:13 +08:00
Diego
6b2e53d6dc 更新配置 2025-05-26 09:17:59 +08:00
2248356998 qq.com
b989aa5561 10.6.21 2025-05-26 00:05:16 +08:00
2248356998 qq.com
f5b0b7ebd2 fix: s7多写 2025-05-24 14:22:58 +08:00
2248356998 qq.com
16881ae076 fix: s7多写 2025-05-24 14:18:58 +08:00
2248356998 qq.com
af04112656 10.6.20
fix: s7多写
fix: opcuaserver写入数组
2025-05-24 14:11:10 +08:00
Diego
a2863112dc 2025-05-23 15:47:41 +08:00
Diego
f531e4dfc5 10.6.19 2025-05-23 13:16:57 +08:00
Diego
8db9b32ba7 10.6.18 2025-05-23 12:55:21 +08:00
Diego
dd5691cbef 更新依赖 2025-05-22 14:40:56 +08:00
91 changed files with 1658 additions and 486 deletions

View File

@@ -64,24 +64,31 @@ public sealed class OperDescAttribute : MoAttribute
public override void OnException(MethodContext context) public override void OnException(MethodContext context)
{ {
//插入异常日志 if (App.HttpContext.Request.Path.StartsWithSegments("/_blazor"))
SysOperateLog log = GetOperLog(LocalizerType, context); {
//插入异常日志
SysOperateLog log = GetOperLog(LocalizerType, context);
log.Category = LogCateGoryEnum.Exception;//操作类型为异常 log.Category = LogCateGoryEnum.Exception;//操作类型为异常
log.ExeStatus = false;//操作状态为失败 log.ExeStatus = false;//操作状态为失败
if (context.Exception is AppFriendlyException exception) if (context.Exception is AppFriendlyException exception)
log.ExeMessage = exception?.Message; log.ExeMessage = exception?.Message;
else else
log.ExeMessage = context.Exception?.ToString(); log.ExeMessage = context.Exception?.ToString();
OperDescAttribute.WriteToQueue(log); OperDescAttribute.WriteToQueue(log);
}
} }
public override void OnSuccess(MethodContext context) public override void OnSuccess(MethodContext context)
{ {
//插入操作日志 if (App.HttpContext.Request.Path.StartsWithSegments("/_blazor"))
SysOperateLog log = GetOperLog(LocalizerType, context); {
OperDescAttribute.WriteToQueue(log);
//插入操作日志
SysOperateLog log = GetOperLog(LocalizerType, context);
OperDescAttribute.WriteToQueue(log);
}
} }
/// <summary> /// <summary>

View File

@@ -15,7 +15,7 @@ namespace ThingsGateway.Admin.Application;
[ApiDescriptionSettings(false)] [ApiDescriptionSettings(false)]
[Route("api/auth")] [Route("api/auth")]
[LoggingMonitor] [RequestAudit]
public class AuthController : ControllerBase public class AuthController : ControllerBase
{ {
private readonly IAuthService _authService; private readonly IAuthService _authService;

View File

@@ -25,7 +25,7 @@ namespace ThingsGateway.Admin.Application;
[Description("登录")] [Description("登录")]
[Route("openapi/auth")] [Route("openapi/auth")]
[Authorize(AuthenticationSchemes = "Bearer")] [Authorize(AuthenticationSchemes = "Bearer")]
[LoggingMonitor] [RequestAudit]
[ApiController] [ApiController]
public class OpenApiController : ControllerBase public class OpenApiController : ControllerBase
{ {

View File

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

View File

@@ -0,0 +1,20 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using ThingsGateway.DependencyInjection;
namespace System;
[SuppressSniffer, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class RequestAuditAttribute : Attribute
{
}

View File

@@ -0,0 +1,98 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public class RequestAuditData
{
/// <summary>
/// 分类
/// </summary>
public string CateGory { get; set; }
/// <summary>
/// 客户端信息
/// </summary>
public UserAgent Client { get; set; }
/// <summary>
/// 请求方法POST/GET
/// </summary>
public string Method { get; set; }
/// <summary>
/// 操作名称
/// </summary>
public string Operation { get; set; }
/// <summary>
/// 请求地址
/// </summary>
public string Path { get; set; }
/// <summary>
/// 方法名称
/// </summary>
public string ActionName { get; set; }
/// <summary>
/// 认证信息
/// </summary>
public List<AuthorizationClaims> AuthorizationClaims { get; set; }
/// <summary>
/// 控制器名
/// </summary>
public string ControllerName { get; set; }
/// <summary>
/// 异常信息
/// </summary>
public LogException Exception { get; set; }
public long TimeOperationElapsedMilliseconds { get; set; }
/// <summary>
/// 服务端
/// </summary>
public string LocalIPv4 { get; set; }
/// <summary>
/// 日志时间
/// </summary>
public DateTimeOffset LogDateTime { get; set; }
/// <summary>
/// 参数列表
/// </summary>
public List<Parameters> Parameters { get; set; }
/// <summary>
/// 客户端IPV4地址
/// </summary>
public string RemoteIPv4 { get; set; }
/// <summary>
/// 请求地址
/// </summary>
public string RequestUrl { get; set; }
/// <summary>
/// 返回信息
/// </summary>
public object ReturnInformation { get; set; }
/// <summary>
/// 验证错误信息
/// </summary>
public Validation Validation { get; set; }
}

View File

@@ -0,0 +1,301 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Logging;
using ThingsGateway.FriendlyException;
using ThingsGateway.Logging;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.UnifyResult;
namespace ThingsGateway.Admin.Application;
public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
{
private const int FilterOrder = -3000;
public int Order => FilterOrder;
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var timeOperation = Stopwatch.StartNew();
var resultContext = await next().ConfigureAwait(false);
// 计算接口执行时间
timeOperation.Stop();
var controllerActionDescriptor = (context.ActionDescriptor as ControllerActionDescriptor);
// 获取动作方法描述器
var actionMethod = controllerActionDescriptor?.MethodInfo;
// 处理 Blazor Server
if (actionMethod == null)
{
return;
}
// 排除 WebSocket 请求处理
if (context.HttpContext.IsWebSocketRequest())
{
return;
}
// 如果贴了 [SuppressMonitor] 特性则跳过
if (actionMethod.IsDefined(typeof(SuppressRequestAuditAttribute), true)
|| actionMethod.DeclaringType.IsDefined(typeof(SuppressRequestAuditAttribute), true))
{
return;
}
// 只有方法贴有特性才进行审计
if (
!actionMethod.DeclaringType.IsDefined(typeof(RequestAuditAttribute), true)
&&
!actionMethod.IsDefined(typeof(RequestAuditAttribute), true))
{
return;
}
var logData = new RequestAuditData();
logData.TimeOperationElapsedMilliseconds = timeOperation.ElapsedMilliseconds;
var resultHttpContext = (resultContext as FilterContext).HttpContext;
// 获取 HttpContext 和 HttpRequest 对象
var httpContext = context.HttpContext;
var httpRequest = httpContext.Request;
// 获取客户端 Ipv4 地址
var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
logData.RemoteIPv4 = remoteIPv4;
var requestUrl = Uri.UnescapeDataString(httpRequest.GetRequestUrlAddress());
logData.RequestUrl = requestUrl;
object returnValue = null;
Type finalReturnType;
var result = resultContext.Result as IActionResult;
// 解析返回值
if (UnifyContext.CheckVaildResult(result, out var data))
{
returnValue = data;
finalReturnType = data?.GetType();
}
// 处理文件类型
else if (result is FileResult fresult)
{
returnValue = new
{
FileName = fresult.FileDownloadName,
fresult.ContentType,
Length = fresult is FileContentResult cresult ? (object)cresult.FileContents.Length : null
};
finalReturnType = fresult?.GetType();
}
else finalReturnType = result?.GetType();
logData.ReturnInformation = returnValue;
//获取客户端信息
var client = App.GetService<IAppService>().UserAgent;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[actionMethod.Name];
//获取特性
logData.CateGory = desc.Value;//传操作名称
logData.Operation = desc.Value;//传操作名称
logData.Client = client;
logData.Path = httpContext.Request.Path.Value;//请求地址
logData.Method = httpContext.Request.Method;//请求方法
logData.ControllerName = controllerActionDescriptor.ControllerName;
logData.ActionName = controllerActionDescriptor.ActionName;
logData.AuthorizationClaims = new();
// 获取授权用户
var user = httpContext.User;
foreach (var claim in user.Claims)
{
logData.AuthorizationClaims.Add(new AuthorizationClaims
{
Type = claim.Type,
Value = claim.Value,
});
}
logData.LocalIPv4 = httpContext.GetLocalIpAddressToIPv4();
logData.LogDateTime = DateTimeOffset.Now;
var parameterValues = context.ActionArguments;
logData.Parameters = new();
var parameters = actionMethod.GetParameters();
foreach (var parameter in parameters)
{
// 判断是否禁用记录特定参数
if (parameter.IsDefined(typeof(SuppressRequestAuditAttribute), false)) continue;
// 排除标记 [FromServices] 的解析
if (parameter.IsDefined(typeof(FromServicesAttribute), false)) continue;
var name = parameter.Name;
var parameterType = parameter.ParameterType;
_ = parameterValues.TryGetValue(name, out var value);
var par = new Parameters()
{
Name = name,
};
logData.Parameters.Add(par);
object rawValue = default;
// 文件类型参数
if (value is IFormFile || value is List<IFormFile>)
{
// 单文件
if (value is IFormFile formFile)
{
var fileSize = Math.Round(formFile.Length / 1024D);
rawValue = new
{
name = formFile.Name,
fileName = formFile.FileName,
length = formFile.Length,
contentType = formFile.ContentType
};
}
// 多文件
else if (value is List<IFormFile> formFiles)
{
var rawValues1 = new List<object>();
for (var i = 0; i < formFiles.Count; i++)
{
var file = formFiles[i];
var size = Math.Round(file.Length / 1024D);
var rawValue1 = new
{
name = file.Name,
fileName = file.FileName,
length = file.Length,
contentType = file.ContentType
};
rawValues1.Add(rawValue1);
}
rawValue = rawValues1;
}
}
// 处理 byte[] 参数类型
else if (value is byte[] byteArray)
{
rawValue = new
{
length = byteArray.Length,
};
}
// 处理基元类型,字符串类型和空值
else if (parameterType.IsPrimitive || value is string || value == null)
{
rawValue = value;
}
// 其他类型统一进行序列化
else
{
rawValue = value;
}
par.Value = rawValue;
}
// 获取异常对象情况
Exception exception = resultContext.Exception;
if (exception is AppFriendlyException friendlyException)
{
logData.Validation = new();
logData.Validation.Message = friendlyException.Message;
}
else if (exception != null)
{
logData.Exception = new();
logData.Exception.Message = exception.Message;
logData.Exception.StackTrace = exception.StackTrace;
logData.Exception.Type = HandleGenericType(exception.GetType());
}
// 创建日志记录器
var logger = httpContext.RequestServices.GetRequiredService<ILogger<RequestAudit>>();
var logContext = new LogContext();
logContext.Set(nameof(RequestAuditData), logData);
// 设置日志上下文
using var scope = logger.ScopeContext(logContext);
if (exception == null)
{
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
}
else
{
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception.ToSystemTextJsonString()}");
}
}
/// <summary>
/// 处理泛型类型转字符串打印问题
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static string HandleGenericType(Type type)
{
if (type == null) return string.Empty;
var typeName = type.FullName ?? (!string.IsNullOrEmpty(type.Namespace) ? type.Namespace + "." : string.Empty) + type.Name;
// 处理泛型类型问题
if (type.IsConstructedGenericType)
{
var prefix = type.GetGenericArguments()
.Select(genericArg => HandleGenericType(genericArg))
.Aggregate((previous, current) => previous + ", " + current);
typeName = typeName.Split('`').First() + "<" + prefix + ">";
}
return typeName;
}
}

View File

@@ -0,0 +1,20 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using ThingsGateway.DependencyInjection;
namespace System;
[SuppressSniffer, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class SuppressRequestAuditAttribute : Attribute
{
}

View File

@@ -16,7 +16,6 @@ using ThingsGateway.Extension;
using ThingsGateway.FriendlyException; using ThingsGateway.FriendlyException;
using ThingsGateway.Logging; using ThingsGateway.Logging;
using ThingsGateway.NewLife.Json.Extension; using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Razor;
namespace ThingsGateway.Admin.Application; namespace ThingsGateway.Admin.Application;
@@ -39,33 +38,31 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
/// <param name="flush"></param> /// <param name="flush"></param>
public async Task WriteAsync(LogMessage logMsg, bool flush) public async Task WriteAsync(LogMessage logMsg, bool flush)
{ {
//获取请求json字符串
var jsonString = logMsg.Context.Get("loggingMonitor").ToString();
//转成实体 //转成实体
var loggingMonitor = jsonString.FromJsonNetString<LoggingMonitorJson>(); var requestAuditData = logMsg.Context.Get(nameof(RequestAuditData)) as RequestAuditData;
//日志时间赋值 //日志时间赋值
loggingMonitor.LogDateTime = logMsg.LogDateTime; requestAuditData.LogDateTime = logMsg.LogDateTime;
// loggingMonitor.ReturnInformation.Value // requestAuditData.ReturnInformation.Value
//验证失败不记录日志 //验证失败不记录日志
bool save = false; bool save = false;
if (loggingMonitor.Validation == null) if (requestAuditData.Validation == null)
{ {
var operation = logMsg.Context.Get(LoggingConst.Operation).ToString();//获取操作名称 var operation = requestAuditData.Operation;//获取操作名称
var client = (UserAgent)logMsg.Context.Get(LoggingConst.Client);//获取客户端信息 var client = requestAuditData.Client;//获取客户端信息
var path = logMsg.Context.Get(LoggingConst.Path).ToString();//获取操作名称 var path = requestAuditData.Path;//获取操作名称
var method = logMsg.Context.Get(LoggingConst.Method).ToString();//获取方法 var method = requestAuditData.Method;//获取方法
//表示访问日志 //表示访问日志
if (path == "/api/auth/login" || path == "/api/auth/logout") if (path == "/api/auth/login" || path == "/api/auth/logout")
{ {
//如果没有异常信息 //如果没有异常信息
if (loggingMonitor.Exception == null) if (requestAuditData.Exception == null)
{ {
save = await CreateVisitLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false);//添加到访问日志 save = await CreateVisitLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);//添加到访问日志
} }
else else
{ {
//添加到异常日志 //添加到异常日志
save = await CreateOperationLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false); save = await CreateOperationLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);
} }
} }
else else
@@ -74,7 +71,7 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
if (!operation.IsNullOrWhiteSpace() && method == "POST") if (!operation.IsNullOrWhiteSpace() && method == "POST")
{ {
//添加到操作日志 //添加到操作日志
save = await CreateOperationLog(operation, path, loggingMonitor, client, flush).ConfigureAwait(false); save = await CreateOperationLog(operation, path, requestAuditData, client, flush).ConfigureAwait(false);
} }
} }
} }
@@ -89,27 +86,21 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
/// </summary> /// </summary>
/// <param name="operation">操作名称</param> /// <param name="operation">操作名称</param>
/// <param name="path">请求地址</param> /// <param name="path">请求地址</param>
/// <param name="loggingMonitor">loggingMonitor</param> /// <param name="requestAuditData">requestAuditData</param>
/// <param name="userAgent">客户端信息</param> /// <param name="userAgent">客户端信息</param>
/// <param name="flush"></param> /// <param name="flush"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> CreateOperationLog(string operation, string path, LoggingMonitorJson loggingMonitor, UserAgent userAgent, bool flush) private async Task<bool> CreateOperationLog(string operation, string path, RequestAuditData requestAuditData, UserAgent userAgent, bool flush)
{ {
//账号 //账号
var opAccount = loggingMonitor.AuthorizationClaims?.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault(); var opAccount = requestAuditData.AuthorizationClaims?.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
//获取参数json字符串 //获取参数json字符串
var paramJson = loggingMonitor.Parameters == null || loggingMonitor.Parameters.Count == 0 ? null : loggingMonitor.Parameters[0].Value.ToSystemTextJsonString(); var paramJson = requestAuditData.Parameters == null || requestAuditData.Parameters.Count == 0 ? null : requestAuditData.Parameters.ToSystemTextJsonString();
//获取结果json字符串 //获取结果json字符串
var resultJson = string.Empty; var resultJson = requestAuditData.ReturnInformation?.ToSystemTextJsonString();
if (loggingMonitor.ReturnInformation != null)//如果有返回值
{
if (loggingMonitor.ReturnInformation.Value != null)//如果返回值不为空
{
resultJson = loggingMonitor.ReturnInformation.Value.ToSystemTextJsonString();
}
}
//操作日志表实体 //操作日志表实体
var sysLogOperate = new SysOperateLog var sysLogOperate = new SysOperateLog
@@ -117,29 +108,29 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
Name = operation, Name = operation,
Category = LogCateGoryEnum.Operate, Category = LogCateGoryEnum.Operate,
ExeStatus = true, ExeStatus = true,
OpIp = loggingMonitor.RemoteIPv4, OpIp = requestAuditData.RemoteIPv4,
OpBrowser = userAgent?.Browser, OpBrowser = userAgent?.Browser,
OpOs = userAgent?.Platform, OpOs = userAgent?.Platform,
OpTime = loggingMonitor.LogDateTime.LocalDateTime, OpTime = requestAuditData.LogDateTime.LocalDateTime,
OpAccount = opAccount, OpAccount = opAccount,
ReqMethod = loggingMonitor.HttpMethod, ReqMethod = requestAuditData.Method,
ReqUrl = path, ReqUrl = path,
ResultJson = resultJson, ResultJson = resultJson,
ClassName = loggingMonitor.DisplayName, ClassName = requestAuditData.ControllerName,
MethodName = loggingMonitor.ActionName, MethodName = requestAuditData.ActionName,
ParamJson = paramJson, ParamJson = paramJson,
VerificatId = UserManager.VerificatId, VerificatId = UserManager.VerificatId,
}; };
//如果异常不为空 //如果异常不为空
if (loggingMonitor.Exception != null) if (requestAuditData.Exception != null)
{ {
sysLogOperate.Category = LogCateGoryEnum.Exception;//操作类型为异常 sysLogOperate.Category = LogCateGoryEnum.Exception;//操作类型为异常
sysLogOperate.ExeStatus = false;//操作状态为失败 sysLogOperate.ExeStatus = false;//操作状态为失败
if (loggingMonitor.Exception.Type == typeof(AppFriendlyException).ToString()) if (requestAuditData.Exception.Type == typeof(AppFriendlyException).ToString())
sysLogOperate.ExeMessage = loggingMonitor?.Exception.Message; sysLogOperate.ExeMessage = requestAuditData?.Exception.Message;
else else
sysLogOperate.ExeMessage = $"{loggingMonitor.Exception.Type}:{loggingMonitor.Exception.Message}{Environment.NewLine}{loggingMonitor.Exception.StackTrace}"; sysLogOperate.ExeMessage = $"{requestAuditData.Exception.Type}:{requestAuditData.Exception.Message}{Environment.NewLine}{requestAuditData.Exception.StackTrace}";
} }
_operateLogMessageQueue.Enqueue(sysLogOperate); _operateLogMessageQueue.Enqueue(sysLogOperate);
@@ -158,26 +149,25 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
/// </summary> /// </summary>
/// <param name="operation">访问类型</param> /// <param name="operation">访问类型</param>
/// <param name="path"></param> /// <param name="path"></param>
/// <param name="loggingMonitor">loggingMonitor</param> /// <param name="requestAuditData">requestAuditData</param>
/// <param name="userAgent">客户端信息</param> /// <param name="userAgent">客户端信息</param>
/// <param name="flush"></param> /// <param name="flush"></param>
private async Task<bool> CreateVisitLog(string operation, string path, LoggingMonitorJson loggingMonitor, UserAgent userAgent, bool flush) private async Task<bool> CreateVisitLog(string operation, string path, RequestAuditData requestAuditData, UserAgent userAgent, bool flush)
{ {
long verificatId = 0;//验证Id long verificatId = 0;//验证Id
var opAccount = "";//用户账号 var opAccount = "";//用户账号
if (path == "/api/auth/login") if (path == "/api/auth/login")
{ {
//如果是登录,用户信息就从返回值里拿 //如果是登录,用户信息就从返回值里拿
var result = loggingMonitor.ReturnInformation?.Value?.ToSystemTextJsonString();//返回值转json dynamic userInfo = requestAuditData.ReturnInformation;
var userInfo = result.FromJsonNetString<UnifyResult<LoginOutput>>();//格式化成user表
opAccount = userInfo.Data.Account;//赋值账号 opAccount = userInfo.Data.Account;//赋值账号
verificatId = userInfo.Data.VerificatId; verificatId = userInfo.Data.VerificatId;
} }
else else
{ {
//如果是登录出用户信息就从AuthorizationClaims里拿 //如果是登录出用户信息就从AuthorizationClaims里拿
opAccount = loggingMonitor.AuthorizationClaims.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault(); opAccount = requestAuditData.AuthorizationClaims.Where(it => it.Type == ClaimConst.Account).Select(it => it.Value).FirstOrDefault();
verificatId = loggingMonitor.AuthorizationClaims.Where(it => it.Type == ClaimConst.VerificatId).Select(it => it.Value).FirstOrDefault().ToLong(); verificatId = requestAuditData.AuthorizationClaims.Where(it => it.Type == ClaimConst.VerificatId).Select(it => it.Value).FirstOrDefault().ToLong();
} }
//日志表实体 //日志表实体
var sysLogVisit = new SysOperateLog var sysLogVisit = new SysOperateLog
@@ -185,19 +175,19 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter
Name = operation, Name = operation,
Category = path == "/api/auth/login" ? LogCateGoryEnum.Login : LogCateGoryEnum.Logout, Category = path == "/api/auth/login" ? LogCateGoryEnum.Login : LogCateGoryEnum.Logout,
ExeStatus = true, ExeStatus = true,
OpIp = loggingMonitor.RemoteIPv4, OpIp = requestAuditData.RemoteIPv4,
OpBrowser = userAgent?.Browser, OpBrowser = userAgent?.Browser,
OpOs = userAgent?.Platform, OpOs = userAgent?.Platform,
OpTime = loggingMonitor.LogDateTime.LocalDateTime, OpTime = requestAuditData.LogDateTime.LocalDateTime,
VerificatId = verificatId, VerificatId = verificatId,
OpAccount = opAccount, OpAccount = opAccount,
ReqMethod = loggingMonitor.HttpMethod, ReqMethod = requestAuditData.Method,
ReqUrl = path, ReqUrl = path,
ResultJson = loggingMonitor.ReturnInformation?.Value?.ToSystemTextJsonString(), ResultJson = requestAuditData.ReturnInformation?.ToSystemTextJsonString(),
ClassName = loggingMonitor.DisplayName, ClassName = requestAuditData.ControllerName,
MethodName = loggingMonitor.ActionName, MethodName = requestAuditData.ActionName,
ParamJson = loggingMonitor.Parameters?.ToSystemTextJsonString(), ParamJson = requestAuditData.Parameters?.ToSystemTextJsonString(),
}; };
_operateLogMessageQueue.Enqueue(sysLogVisit); _operateLogMessageQueue.Enqueue(sysLogVisit);

View File

@@ -16,9 +16,9 @@ namespace ThingsGateway.Admin.Application;
/// 内存推送事件服务 /// 内存推送事件服务
/// </summary> /// </summary>
/// <typeparam name="TEntry"></typeparam> /// <typeparam name="TEntry"></typeparam>
public class EventService<TEntry> : IEventService<TEntry> public class EventService<TEntry> : IEventService<TEntry>, IDisposable
{ {
private ConcurrentDictionary<string, Func<TEntry, Task>> Cache { get; } = new(); private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new();
public void Dispose() public void Dispose()
{ {

View File

@@ -28,7 +28,7 @@ public class UserIdProvider : IUserIdProvider
if (UserId > 0) if (UserId > 0)
{ {
return $"{UserId}{SysHub.Separate}{YitIdHelper.NextId()}";//返回用户ID return $"{UserId}{SysHub.Separate}{CommonUtils.GetSingleId()}";//返回用户ID
} }
return connection.ConnectionId; return connection.ConnectionId;

View File

@@ -117,6 +117,25 @@ public class SugarAopService : ISugarAopService
db.Aop.DataExecuted = (value, entity) => db.Aop.DataExecuted = (value, entity) =>
{ {
}; };
db.Aop.OnLogExecuted = (sql, pars) =>
{
//执行时间超过1秒
if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
{
//代码CS文件名
var fileName = db.Ado.SqlStackTrace.FirstFileName;
//代码行数
var fileLine = db.Ado.SqlStackTrace.FirstLine;
//方法名
var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
DbContext.WriteLog($"{fileName}-{FirstMethodName}-{fileLine} 执行时间超过1秒");
DbContext.WriteLogWithSql(UtilMethods.GetNativeSql(sql, pars));
}
};
} }
} }

View File

@@ -80,7 +80,9 @@ public static class DbContext
{ {
db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings
{ {
SqlServerCodeFirstNvarchar = true//设置默认nvarchar SqlServerCodeFirstNvarchar = true, //设置默认nvarchar
IsNoReadXmlDescription = true
}; };
} }

View File

@@ -37,7 +37,7 @@ public sealed class SqlSugarOption : ConnectionConfig
/// <summary> /// <summary>
/// 是否控制台显示Sql语句 /// 是否控制台显示Sql语句
/// </summary> /// </summary>
public bool IsShowSql { get; set; } public bool? IsShowSql { get; set; }
/// <summary> /// <summary>
/// 更新数据 /// 更新数据

View File

@@ -98,6 +98,21 @@ public class Startup : AppStartup
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
try
{
using var db = DbContext.GetDB<SysOperateLog>();
if (db.CurrentConnectionConfig.DbType == SqlSugar.DbType.Sqlite)
{
if (!db.DbMaintenance.IsAnyIndex("idx_operatelog_optime_date"))
{
var indexsql = "CREATE INDEX idx_operatelog_optime_date ON sys_operatelog(strftime('%Y-%m-%d', OpTime));";
db.Ado.ExecuteCommand(indexsql);
}
}
}
catch { }
//删除在线用户统计 //删除在线用户统计
var verificatInfoService = App.RootServices.GetService<IVerificatInfoService>(); var verificatInfoService = App.RootServices.GetService<IVerificatInfoService>();
verificatInfoService.RemoveAllClientId(); verificatInfoService.RemoveAllClientId();

View File

@@ -18,7 +18,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.4" /> <PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.5" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" /> <PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.193" /> <PackageReference Include="SqlSugarCore" Version="5.1.4.193" />
</ItemGroup> </ItemGroup>

View File

@@ -97,7 +97,7 @@ public class BlazorAppContext
AllResource = sysResources; AllResource = sysResources;
var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet(); var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet();
CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList(); CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList();
AllMenus = sysResources.Where(a => a.Category == ResourceCategoryEnum.Menu); AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu);
if (moduleId == null) if (moduleId == null)
{ {

View File

@@ -18,8 +18,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.Extension; using ThingsGateway.Extension;

View File

@@ -14,7 +14,6 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -29,7 +28,6 @@ using System.Text.Unicode;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.Extension; using ThingsGateway.Extension;
using ThingsGateway.Logging;
using ThingsGateway.NewLife.Caching; using ThingsGateway.NewLife.Caching;
namespace ThingsGateway.AdminServer; namespace ThingsGateway.AdminServer;
@@ -89,6 +87,7 @@ public class Startup : AppStartup
} }
; ;
services.AddMvcFilter<RequestAuditFilter>();
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings)) .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings))
//.AddXmlSerializerFormatters() //.AddXmlSerializerFormatters()
@@ -161,7 +160,9 @@ public class Startup : AppStartup
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return true; if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested && logMsg.LogLevel >= LogLevel.Warning) return false;
if (string.IsNullOrEmpty(logMsg.Message)) return false;
else return true;
}; };
options.MessageFormat = (logMsg) => options.MessageFormat = (logMsg) =>
@@ -211,39 +212,39 @@ public class Startup : AppStartup
#region api日志 #region api日志
//Monitor日志配置 //Monitor日志配置
services.AddMonitorLogging(options => //services.AddMonitorLogging(options =>
{ //{
options.JsonIndented = true;// 是否美化 JSON // options.JsonIndented = true;// 是否美化 JSON
options.GlobalEnabled = false;//全局启用 // options.GlobalEnabled = false;//全局启用
options.ConfigureLogger((logger, logContext, context) => // options.ConfigureLogger((logger, logContext, context) =>
{ // {
var httpContext = context.HttpContext;//获取httpContext // var httpContext = context.HttpContext;//获取httpContext
//获取客户端信息 // //获取客户端信息
var client = App.GetService<IAppService>().UserAgent; // var client = App.GetService<IAppService>().UserAgent;
// 获取控制器/操作描述器 // // 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; // var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性 // //操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}"; // var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name]; // var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name];
//获取特性 // //获取特性
option = desc.Value;//则将操作名称赋值为控制器上写的title // option = desc.Value;//则将操作名称赋值为控制器上写的title
logContext.Set(LoggingConst.CateGory, option);//传操作名称 // logContext.Set(LoggingConst.CateGory, option);//传操作名称
logContext.Set(LoggingConst.Operation, option);//传操作名称 // logContext.Set(LoggingConst.Operation, option);//传操作名称
logContext.Set(LoggingConst.Client, client);//客户端信息 // logContext.Set(LoggingConst.Client, client);//客户端信息
logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址 // logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址
logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法 // logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法
}); // });
}); //});
//日志写入数据库配置 //日志写入数据库配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options => services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return logMsg.LogName == "System.Logging.LoggingMonitor";//只写入LoggingMonitor日志 return logMsg.LogName == "System.Logging.RequestAudit";
}; };
}); });

View File

@@ -71,13 +71,25 @@ public static class App
/// </summary> /// </summary>
public static IServiceProvider RootServices => InternalApp.RootServices; public static IServiceProvider RootServices => InternalApp.RootServices;
private static IHostApplicationLifetime hostApplicationLifetime;
public static IHostApplicationLifetime HostApplicationLifetime
{
get
{
if ((hostApplicationLifetime == null))
{
hostApplicationLifetime = RootServices?.GetService<IHostApplicationLifetime>();
}
return hostApplicationLifetime;
}
}
private static IStringLocalizerFactory? stringLocalizerFactory; private static IStringLocalizerFactory? stringLocalizerFactory;
/// <summary> /// <summary>
/// 本地化服务工厂 /// 本地化服务工厂
/// </summary> /// </summary>
public static IStringLocalizerFactory? StringLocalizerFactory public static IStringLocalizerFactory? StringLocalizerFactory
{ {
get get
{ {

View File

@@ -25,7 +25,7 @@ public static class ILoggerExtensions
/// <param name="logger"></param> /// <param name="logger"></param>
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param> /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
/// <returns></returns> /// <returns></returns>
public static IDisposable ScopeContext(this ILogger logger, IDictionary<object, object> properties) public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties)
{ {
if (logger == null) throw new ArgumentNullException(nameof(logger)); if (logger == null) throw new ArgumentNullException(nameof(logger));

View File

@@ -26,11 +26,11 @@ public static class LogContextExtensions
/// <param name="key">键</param> /// <param name="key">键</param>
/// <param name="value">值</param> /// <param name="value">值</param>
/// <returns></returns> /// <returns></returns>
public static LogContext Set(this LogContext logContext, object key, object value) public static LogContext Set(this LogContext logContext, string key, object value)
{ {
if (logContext == null || key == null) return logContext; if (logContext == null || key == null) return logContext;
logContext.Properties ??= new Dictionary<object, object>(); logContext.Properties ??= new Dictionary<string, object>();
logContext.Properties.Remove(key); logContext.Properties.Remove(key);
logContext.Properties.Add(key, value); logContext.Properties.Add(key, value);
@@ -43,7 +43,7 @@ public static class LogContextExtensions
/// <param name="logContext"></param> /// <param name="logContext"></param>
/// <param name="properties"></param> /// <param name="properties"></param>
/// <returns></returns> /// <returns></returns>
public static LogContext SetRange(this LogContext logContext, IDictionary<object, object> properties) public static LogContext SetRange(this LogContext logContext, IDictionary<string, object> properties)
{ {
if (logContext == null if (logContext == null
|| properties == null || properties == null
@@ -63,7 +63,7 @@ public static class LogContextExtensions
/// <param name="logContext"></param> /// <param name="logContext"></param>
/// <param name="key">键</param> /// <param name="key">键</param>
/// <returns></returns> /// <returns></returns>
public static object Get(this LogContext logContext, object key) public static object Get(this LogContext logContext, string key)
{ {
if (logContext == null if (logContext == null
|| key == null || key == null
@@ -80,7 +80,7 @@ public static class LogContextExtensions
/// <param name="logContext"></param> /// <param name="logContext"></param>
/// <param name="key">键</param> /// <param name="key">键</param>
/// <returns></returns> /// <returns></returns>
public static T Get<T>(this LogContext logContext, object key) public static T Get<T>(this LogContext logContext, string key)
{ {
var value = logContext.Get(key); var value = logContext.Get(key);
return value.ChangeType<T>(); return value.ChangeType<T>();

View File

@@ -84,7 +84,7 @@ public static class StringLoggingExtensions
/// <param name="message"></param> /// <param name="message"></param>
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param> /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
/// <returns></returns> /// <returns></returns>
public static StringLoggingPart ScopeContext(this string message, IDictionary<object, object> properties) public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties)
{ {
return StringLoggingPart.Default().SetMessage(message).ScopeContext(properties); return StringLoggingPart.Default().SetMessage(message).ScopeContext(properties);
} }

View File

@@ -20,7 +20,7 @@ namespace ThingsGateway.Logging;
/// </summary> /// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks> /// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
[SuppressSniffer] [SuppressSniffer]
public sealed class DatabaseLogger : ILogger public sealed class DatabaseLogger : ILogger, IDisposable
{ {
/// <summary> /// <summary>
/// 记录器类别名称 /// 记录器类别名称
@@ -60,6 +60,11 @@ public sealed class DatabaseLogger : ILogger
return _databaseLoggerProvider.ScopeProvider?.Push(state); return _databaseLoggerProvider.ScopeProvider?.Push(state);
} }
public void Dispose()
{
_databaseLoggerProvider.RemoveCache(_logName);
}
/// <summary> /// <summary>
/// 检查是否已启用给定日志级别 /// 检查是否已启用给定日志级别
/// </summary> /// </summary>

View File

@@ -14,6 +14,8 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ThingsGateway.Extension.Generic;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -54,6 +56,8 @@ public sealed class DatabaseLoggerProvider : ILoggerProvider, ISupportExternalSc
/// <remarks>实现不间断写入</remarks> /// <remarks>实现不间断写入</remarks>
private Task _processQueueTask; private Task _processQueueTask;
/// <summary> /// <summary>
/// 构造函数 /// 构造函数
/// </summary> /// </summary>
@@ -82,7 +86,10 @@ public sealed class DatabaseLoggerProvider : ILoggerProvider, ISupportExternalSc
{ {
return _databaseLoggers.GetOrAdd(categoryName, name => new DatabaseLogger(name, this)); return _databaseLoggers.GetOrAdd(categoryName, name => new DatabaseLogger(name, this));
} }
public void RemoveCache(string categoryName)
{
_databaseLoggers.Remove(categoryName);
}
/// <summary> /// <summary>
/// 设置作用域提供器 /// 设置作用域提供器
/// </summary> /// </summary>

View File

@@ -18,8 +18,17 @@ namespace ThingsGateway.Logging;
/// </summary> /// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks> /// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
[SuppressSniffer] [SuppressSniffer]
public sealed class EmptyLogger : ILogger public sealed class EmptyLogger : ILogger, IDisposable
{ {
public EmptyLogger(string categoryName, EmptyLoggerProvider emptyLoggerProvider)
{
_logName = categoryName;
_emptyLoggerProvider = emptyLoggerProvider;
}
private string _logName { get; }
private EmptyLoggerProvider _emptyLoggerProvider { get; }
/// <summary> /// <summary>
/// 开始逻辑操作范围 /// 开始逻辑操作范围
/// </summary> /// </summary>
@@ -31,6 +40,11 @@ public sealed class EmptyLogger : ILogger
return default; return default;
} }
public void Dispose()
{
_emptyLoggerProvider.RemoveCache(_logName);
}
/// <summary> /// <summary>
/// 检查是否已启用给定日志级别 /// 检查是否已启用给定日志级别
/// </summary> /// </summary>

View File

@@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ThingsGateway.Extension.Generic;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -34,9 +36,12 @@ public sealed class EmptyLoggerProvider : ILoggerProvider
/// <returns><see cref="ILogger"/></returns> /// <returns><see cref="ILogger"/></returns>
public ILogger CreateLogger(string categoryName) public ILogger CreateLogger(string categoryName)
{ {
return _emptyLoggers.GetOrAdd(categoryName, name => new EmptyLogger()); return _emptyLoggers.GetOrAdd(categoryName, name => new EmptyLogger(categoryName, this));
}
public void RemoveCache(string categoryName)
{
_emptyLoggers.Remove(categoryName);
} }
/// <summary> /// <summary>
/// 释放非托管资源 /// 释放非托管资源
/// </summary> /// </summary>

View File

@@ -18,7 +18,7 @@ namespace ThingsGateway.Logging;
/// </summary> /// </summary>
/// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks> /// <remarks>https://docs.microsoft.com/zh-cn/dotnet/core/extensions/custom-logging-provider</remarks>
[SuppressSniffer] [SuppressSniffer]
public sealed class FileLogger : ILogger public sealed class FileLogger : ILogger, IDisposable
{ {
/// <summary> /// <summary>
/// 记录器类别名称 /// 记录器类别名称
@@ -58,6 +58,11 @@ public sealed class FileLogger : ILogger
return _fileLoggerProvider.ScopeProvider?.Push(state); return _fileLoggerProvider.ScopeProvider?.Push(state);
} }
public void Dispose()
{
_fileLoggerProvider.RemoveCache(_logName);
}
/// <summary> /// <summary>
/// 检查是否已启用给定日志级别 /// 检查是否已启用给定日志级别
/// </summary> /// </summary>

View File

@@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ThingsGateway.Extension.Generic;
namespace ThingsGateway.Logging; namespace ThingsGateway.Logging;
/// <summary> /// <summary>
@@ -116,6 +118,10 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
{ {
return _fileLoggers.GetOrAdd(categoryName, name => new FileLogger(name, this)); return _fileLoggers.GetOrAdd(categoryName, name => new FileLogger(name, this));
} }
public void RemoveCache(string categoryName)
{
_fileLoggers.Remove(categoryName);
}
/// <summary> /// <summary>
/// 设置作用域提供器 /// 设置作用域提供器

View File

@@ -17,11 +17,10 @@ namespace ThingsGateway.Logging;
[SuppressSniffer] [SuppressSniffer]
public sealed class LogContext : IDisposable public sealed class LogContext : IDisposable
{ {
/// <summary> /// <summary>
/// 日志上下文数据 /// 日志上下文数据
/// </summary> /// </summary>
public IDictionary<object, object> Properties { get; set; } public IDictionary<string, object> Properties { get; set; }
/// <summary> /// <summary>
/// 原生日志上下文数据 /// 原生日志上下文数据

View File

@@ -96,7 +96,7 @@ public sealed partial class StringLoggingPart
/// </summary> /// </summary>
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param> /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
/// <returns></returns> /// <returns></returns>
public StringLoggingPart ScopeContext(IDictionary<object, object> properties) public StringLoggingPart ScopeContext(IDictionary<string, object> properties)
{ {
if (properties == null) return this; if (properties == null) return this;
LogContext = new LogContext { Properties = properties }; LogContext = new LogContext { Properties = properties };

View File

@@ -59,7 +59,7 @@ public static class Log
/// </summary> /// </summary>
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param> /// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
/// <returns></returns> /// <returns></returns>
public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<object, object> properties) public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties)
{ {
return GetLogger(StringLoggingPart.Default().ScopeContext(properties)); return GetLogger(StringLoggingPart.Default().ScopeContext(properties));
} }

View File

@@ -39,7 +39,7 @@
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="Mapster" Version="7.4.0" /> <PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" /> <PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">

View File

@@ -349,7 +349,7 @@ public static class UnifyContext
/// <param name="result"></param> /// <param name="result"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <returns></returns> /// <returns></returns>
internal static bool CheckVaildResult(IActionResult result, out object data) public static bool CheckVaildResult(IActionResult result, out object data)
{ {
data = default; data = default;

View File

@@ -1,7 +1,4 @@
using System.Buffers; using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.NewLife.Buffers; namespace ThingsGateway.NewLife.Buffers;
@@ -83,7 +80,7 @@ internal sealed class BufferSegment : ReadOnlySequenceSegment<Byte>
} }
else if (_array != null) else if (_array != null)
{ {
Pool.Shared.Return(_array); ArrayPool<Byte>.Shared.Return(_array);
_array = null; _array = null;
} }
base.Memory = default; base.Memory = default;

View File

@@ -1,9 +1,4 @@
using System.Buffers; #if NETFRAMEWORK || NETSTANDARD2_0
using ThingsGateway.NewLife.Collections;
#if NETFRAMEWORK || NETSTANDARD2_0
using ValueTask = System.Threading.Tasks.Task; using ValueTask = System.Threading.Tasks.Task;
#endif #endif
@@ -28,7 +23,7 @@ public sealed class PooledByteBufferWriter : IBufferWriter<Byte>, IDisposable
/// <param name="initialCapacity"></param> /// <param name="initialCapacity"></param>
public PooledByteBufferWriter(Int32 initialCapacity) public PooledByteBufferWriter(Int32 initialCapacity)
{ {
_rentedBuffer = Pool.Shared.Rent(initialCapacity); _rentedBuffer = ArrayPool<Byte>.Shared.Rent(initialCapacity);
_index = 0; _index = 0;
} }
@@ -45,7 +40,7 @@ public sealed class PooledByteBufferWriter : IBufferWriter<Byte>, IDisposable
/// <param name="initialCapacity"></param> /// <param name="initialCapacity"></param>
public void InitializeEmptyInstance(Int32 initialCapacity) public void InitializeEmptyInstance(Int32 initialCapacity)
{ {
_rentedBuffer = Pool.Shared.Rent(initialCapacity); _rentedBuffer = ArrayPool<Byte>.Shared.Rent(initialCapacity);
_index = 0; _index = 0;
} }
@@ -63,7 +58,7 @@ public sealed class PooledByteBufferWriter : IBufferWriter<Byte>, IDisposable
var rentedBuffer = _rentedBuffer; var rentedBuffer = _rentedBuffer;
_rentedBuffer = null!; _rentedBuffer = null!;
Pool.Shared.Return(rentedBuffer); ArrayPool<Byte>.Shared.Return(rentedBuffer);
} }
/// <summary>通知 IBufferWriter已向输出写入 count 数据项。</summary> /// <summary>通知 IBufferWriter已向输出写入 count 数据项。</summary>
@@ -119,11 +114,11 @@ public sealed class PooledByteBufferWriter : IBufferWriter<Byte>, IDisposable
} }
} }
var rentedBuffer = _rentedBuffer; var rentedBuffer = _rentedBuffer;
_rentedBuffer = Pool.Shared.Rent(num4); _rentedBuffer = ArrayPool<Byte>.Shared.Rent(num4);
var span = rentedBuffer.AsSpan(0, _index); var span = rentedBuffer.AsSpan(0, _index);
span.CopyTo(_rentedBuffer); span.CopyTo(_rentedBuffer);
span.Clear(); span.Clear();
Pool.Shared.Return(rentedBuffer); ArrayPool<Byte>.Shared.Return(rentedBuffer);
} }
#endregion #endregion
} }

View File

@@ -241,7 +241,7 @@ public static class SpanHelper
return; return;
} }
var array = Pool.Shared.Rent(buffer.Length); var array = ArrayPool<Byte>.Shared.Rent(buffer.Length);
try try
{ {
@@ -251,7 +251,7 @@ public static class SpanHelper
} }
finally finally
{ {
Pool.Shared.Return(array); ArrayPool<Byte>.Shared.Return(array);
} }
} }
@@ -265,7 +265,7 @@ public static class SpanHelper
if (MemoryMarshal.TryGetArray(buffer, out var segment)) if (MemoryMarshal.TryGetArray(buffer, out var segment))
return stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken); return stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken);
var array = Pool.Shared.Rent(buffer.Length); var array = ArrayPool<Byte>.Shared.Rent(buffer.Length);
buffer.Span.CopyTo(array); buffer.Span.CopyTo(array);
var writeTask = stream.WriteAsync(array, 0, buffer.Length, cancellationToken); var writeTask = stream.WriteAsync(array, 0, buffer.Length, cancellationToken);
@@ -277,7 +277,7 @@ public static class SpanHelper
} }
finally finally
{ {
Pool.Shared.Return(array); ArrayPool<Byte>.Shared.Return(array);
} }
}, cancellationToken); }, cancellationToken);
} }

View File

@@ -1,5 +1,4 @@
using System.Buffers; using System.Text;
using System.Text;
namespace ThingsGateway.NewLife.Collections; namespace ThingsGateway.NewLife.Collections;
@@ -95,7 +94,7 @@ public static class Pool
{ {
//if (ms == null) return null; //if (ms == null) return null;
var buf = returnResult ? ms.ToArray() : Empty; var buf = returnResult ? ms.ToArray() : Array.Empty<byte>();
Pool.MemoryStream.Return(ms); Pool.MemoryStream.Return(ms);
@@ -133,11 +132,5 @@ public static class Pool
} }
#endregion #endregion
#region ByteArray
/// <summary>字节数组共享存储</summary>
public static ArrayPool<Byte> Shared { get; set; } = ArrayPool<Byte>.Shared;
/// <summary>空数组</summary>
public static Byte[] Empty { get; } = [];
#endregion
} }

View File

@@ -2,8 +2,6 @@
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.NewLife.Extension; namespace ThingsGateway.NewLife.Extension;
/// <summary>工具类</summary> /// <summary>工具类</summary>
@@ -452,12 +450,12 @@ public class DefaultConvert
// 凑够8字节 // 凑够8字节
if (buf.Length < 8) if (buf.Length < 8)
{ {
var bts = Pool.Shared.Rent(8); var bts = ArrayPool<Byte>.Shared.Rent(8);
Buffer.BlockCopy(buf, 0, bts, 0, buf.Length); Buffer.BlockCopy(buf, 0, bts, 0, buf.Length);
var dec = BitConverter.ToDouble(bts, 0).ToDecimal(); var dec = BitConverter.ToDouble(bts, 0).ToDecimal();
Pool.Shared.Return(bts); ArrayPool<Byte>.Shared.Return(bts);
return dec; return dec;
} }

View File

@@ -37,7 +37,8 @@ public static class SystemTextJsonExtension
{ {
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true, // 缩进 WriteIndented = true, // 缩进
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull // 忽略 null DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
}; };
// 如有自定义Converter这里添加 // 如有自定义Converter这里添加
// IndentedOptions.Converters.Add(new ByteArrayJsonConverter()); // IndentedOptions.Converters.Add(new ByteArrayJsonConverter());
@@ -50,7 +51,8 @@ public static class SystemTextJsonExtension
{ {
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false, // 不缩进 WriteIndented = false, // 不缩进
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
}; };
NoneIndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson()); NoneIndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
NoneIndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter()); NoneIndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());

View File

@@ -8,4 +8,6 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
global using System.Buffers;
global using ThingsGateway.NewLife.Extension; global using ThingsGateway.NewLife.Extension;

View File

@@ -5,7 +5,6 @@ using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using ThingsGateway.NewLife.Caching; using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Net; using ThingsGateway.NewLife.Net;
namespace ThingsGateway.NewLife; namespace ThingsGateway.NewLife;
@@ -52,7 +51,7 @@ public static class NetHelper
#endif #endif
{ {
UInt32 dummy = 0; UInt32 dummy = 0;
var inOptionValues = Pool.Shared.Rent(Marshal.SizeOf(dummy) * 3); var inOptionValues = ArrayPool<Byte>.Shared.Rent(Marshal.SizeOf(dummy) * 3);
// 是否启用Keep-Alive // 是否启用Keep-Alive
BitConverter.GetBytes((UInt32)(isKeepAlive ? 1 : 0)).CopyTo(inOptionValues, 0); BitConverter.GetBytes((UInt32)(isKeepAlive ? 1 : 0)).CopyTo(inOptionValues, 0);
@@ -63,7 +62,7 @@ public static class NetHelper
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
Pool.Shared.Return(inOptionValues); ArrayPool<Byte>.Shared.Return(inOptionValues);
return; return;
} }
@@ -538,11 +537,11 @@ public static class NetHelper
private static void Wake(String mac) private static void Wake(String mac)
{ {
mac = mac.Replace("-", null).Replace(":", null); mac = mac.Replace("-", null).Replace(":", null);
var buffer = Pool.Shared.Rent(mac.Length / 2); var buffer = ArrayPool<Byte>.Shared.Rent(mac.Length / 2);
for (var i = 0; i < buffer.Length; i++) for (var i = 0; i < buffer.Length; i++)
buffer[i] = Byte.Parse(mac.Substring(i * 2, 2), NumberStyles.HexNumber); buffer[i] = Byte.Parse(mac.Substring(i * 2, 2), NumberStyles.HexNumber);
var bts = Pool.Shared.Rent(6 + 16 * buffer.Length); var bts = ArrayPool<Byte>.Shared.Rent(6 + 16 * buffer.Length);
for (var i = 0; i < 6; i++) for (var i = 0; i < 6; i++)
bts[i] = 0xFF; bts[i] = 0xFF;
for (Int32 i = 6, k = 0; i < bts.Length; i++, k++) for (Int32 i = 6, k = 0; i < bts.Length; i++, k++)
@@ -560,8 +559,8 @@ public static class NetHelper
client.Close(); client.Close();
//client.SendAsync(bts, bts.Length, new IPEndPoint(IPAddress.Broadcast, 7)); //client.SendAsync(bts, bts.Length, new IPEndPoint(IPAddress.Broadcast, 7));
Pool.Shared.Return(bts); ArrayPool<Byte>.Shared.Return(bts);
Pool.Shared.Return(buffer); ArrayPool<Byte>.Shared.Return(buffer);
} }
#endregion #endregion

View File

@@ -1,7 +1,6 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Reflection; using ThingsGateway.NewLife.Reflection;
namespace ThingsGateway.NewLife.Serialization; namespace ThingsGateway.NewLife.Serialization;
@@ -156,7 +155,7 @@ public class Binary : FormatterBase, IBinary
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
Stream.Write(buffer); Stream.Write(buffer);
#else #else
var array = Pool.Shared.Rent(buffer.Length); var array = ArrayPool<Byte>.Shared.Rent(buffer.Length);
try try
{ {
buffer.CopyTo(array); buffer.CopyTo(array);
@@ -165,7 +164,7 @@ public class Binary : FormatterBase, IBinary
} }
finally finally
{ {
Pool.Shared.Return(array); ArrayPool<Byte>.Shared.Return(array);
} }
#endif #endif
} }
@@ -494,7 +493,7 @@ public class Binary : FormatterBase, IBinary
/// <param name="max"></param> /// <param name="max"></param>
public void WriteBCD(String value, Int32 max) public void WriteBCD(String value, Int32 max)
{ {
var buf = Pool.Shared.Rent(max); var buf = ArrayPool<Byte>.Shared.Rent(max);
for (Int32 i = 0, j = 0; i < max && j + 1 < value.Length; i++, j += 2) for (Int32 i = 0, j = 0; i < max && j + 1 < value.Length; i++, j += 2)
{ {
var a = (Byte)(value[j] - '0'); var a = (Byte)(value[j] - '0');
@@ -503,7 +502,7 @@ public class Binary : FormatterBase, IBinary
} }
Write(buf, 0, max); Write(buf, 0, max);
Pool.Shared.Return(buf); ArrayPool<Byte>.Shared.Return(buf);
} }
/// <summary>写入定长字符串。多余截取,少则补零</summary> /// <summary>写入定长字符串。多余截取,少则补零</summary>
@@ -511,11 +510,11 @@ public class Binary : FormatterBase, IBinary
/// <param name="max"></param> /// <param name="max"></param>
public void WriteFixedString(String? value, Int32 max) public void WriteFixedString(String? value, Int32 max)
{ {
var buf = Pool.Shared.Rent(max); var buf = ArrayPool<Byte>.Shared.Rent(max);
if (!value.IsNullOrEmpty()) Encoding.GetBytes(value, 0, value.Length, buf, 0); if (!value.IsNullOrEmpty()) Encoding.GetBytes(value, 0, value.Length, buf, 0);
Write(buf, 0, max); Write(buf, 0, max);
Pool.Shared.Return(buf); ArrayPool<Byte>.Shared.Return(buf);
} }
/// <summary>读取定长字符串。多余截取,少则补零</summary> /// <summary>读取定长字符串。多余截取,少则补零</summary>

View File

@@ -1,7 +1,6 @@
using System.Globalization; using System.Globalization;
using System.Xml; using System.Xml;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Reflection; using ThingsGateway.NewLife.Reflection;
namespace ThingsGateway.NewLife.Serialization; namespace ThingsGateway.NewLife.Serialization;
@@ -144,10 +143,10 @@ public class XmlGeneral : XmlHandlerBase
else if (type == typeof(Byte[])) else if (type == typeof(Byte[]))
{ {
// 用字符串长度作为预设缓冲区的长度 // 用字符串长度作为预设缓冲区的长度
var buf = Pool.Shared.Rent(reader.Value.Length); var buf = ArrayPool<Byte>.Shared.Rent(reader.Value.Length);
var count = reader.ReadContentAsBase64(buf, 0, buf.Length); var count = reader.ReadContentAsBase64(buf, 0, buf.Length);
value = buf.ReadBytes(0, count); value = buf.ReadBytes(0, count);
Pool.Shared.Return(buf); ArrayPool<Byte>.Shared.Return(buf);
return true; return true;
} }

View File

@@ -24,9 +24,8 @@ public static class ParallelExtensions
/// <param name="body">要执行的操作</param> /// <param name="body">要执行的操作</param>
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body) public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body)
{ {
// 创建并行操作的选项对象,设置最大并行度为当前处理器数量的一半
ParallelOptions options = new(); ParallelOptions options = new();
options.MaxDegreeOfParallelism = Environment.ProcessorCount / 2 == 0 ? 1 : Environment.ProcessorCount / 2; options.MaxDegreeOfParallelism = Environment.ProcessorCount;
// 使用 Parallel.ForEach 执行指定的操作 // 使用 Parallel.ForEach 执行指定的操作
Parallel.ForEach(source, options, (variable) => Parallel.ForEach(source, options, (variable) =>
{ {
@@ -42,9 +41,8 @@ public static class ParallelExtensions
/// <param name="body">要执行的操作</param> /// <param name="body">要执行的操作</param>
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T, ParallelLoopState, long> body) public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T, ParallelLoopState, long> body)
{ {
// 创建并行操作的选项对象,设置最大并行度为当前处理器数量的一半
ParallelOptions options = new(); ParallelOptions options = new();
options.MaxDegreeOfParallelism = Environment.ProcessorCount / 2 == 0 ? 1 : Environment.ProcessorCount / 2; options.MaxDegreeOfParallelism = Environment.ProcessorCount;
// 使用 Parallel.ForEach 执行指定的操作 // 使用 Parallel.ForEach 执行指定的操作
Parallel.ForEach(source, options, (variable, state, index) => Parallel.ForEach(source, options, (variable, state, index) =>
{ {

View File

@@ -27,6 +27,13 @@ public class Startup : AppStartup
{ {
services.AddBootstrapBlazor( services.AddBootstrapBlazor(
option => option.JSModuleVersion = Random.Shared.Next(10000).ToString() option => option.JSModuleVersion = Random.Shared.Next(10000).ToString()
, jsonLocalizationOptions =>
{
jsonLocalizationOptions.DisableGetLocalizerFromResourceManager = true;
jsonLocalizationOptions.DisableGetLocalizerFromService = true;
jsonLocalizationOptions.IgnoreLocalizerMissing = true;
jsonLocalizationOptions.UseKeyWhenValueIsNull = true;
}
); );
services.AddConfigurableOptions<MenuOptions>(); services.AddConfigurableOptions<MenuOptions>();
services.ConfigureIconThemeOptions(options => options.ThemeKey = "fa"); services.ConfigureIconThemeOptions(options => options.ThemeKey = "fa");

View File

@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" /> <PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor" Version="9.6.3" /> <PackageReference Include="BootstrapBlazor" Version="9.6.4" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup> </ItemGroup>

View File

@@ -1,5 +1,5 @@
//下载文件 //下载文件
export function blazor_downloadFile(url, fileName, dtoObject) { export async function blazor_downloadFile(url, fileName, dtoObject) {
const params = new URLSearchParams(); const params = new URLSearchParams();
// 将 dtoObject 的属性添加到 URLSearchParams 中 // 将 dtoObject 的属性添加到 URLSearchParams 中
@@ -12,97 +12,92 @@ export function blazor_downloadFile(url, fileName, dtoObject) {
// 构建完整的 URL // 构建完整的 URL
const fullUrl = `${url}?${params.toString()}`; const fullUrl = `${url}?${params.toString()}`;
// 发起 fetch 请求 try {
fetch(fullUrl) // 发起 fetch 请求
.then(response => { const response = await fetch(fullUrl);
// 获取响应头中的 content-disposition
const dispositionHeader = response.headers.get('content-disposition');
let resolvedFileName = fileName;
if (dispositionHeader) { // 获取响应头中的 content-disposition
// 解析出文件名 const dispositionHeader = response.headers.get('content-disposition');
const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(dispositionHeader); let resolvedFileName = fileName;
const serverFileName = match && match[1] ? match[1].replace(/['"]/g, '') : null;
if (serverFileName) { if (dispositionHeader) {
resolvedFileName = serverFileName; // 解析出文件名
} const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(dispositionHeader);
const serverFileName = match && match[1] ? match[1].replace(/['"]/g, '') : null;
if (serverFileName) {
resolvedFileName = serverFileName;
} }
}
// 将响应转换为 blob 对象 // 将响应转换为 blob 对象
return response.blob().then(blob => { const blob = await response.blob();
// 创建临时的文件 URL
const fileUrl = window.URL.createObjectURL(blob);
// 创建一个 <a> 元素并设置下载链接和文件名 // 创建临时的文件 URL
const anchorElement = document.createElement('a'); const fileUrl = window.URL.createObjectURL(blob);
anchorElement.href = fileUrl;
anchorElement.download = resolvedFileName;
anchorElement.style.display = 'none';
// <a> 元素添加到 body 中并触发下载 // 创建一个 <a> 元素并设置下载链接和文件名
document.body.appendChild(anchorElement); const anchorElement = document.createElement('a');
anchorElement.click(); anchorElement.href = fileUrl;
document.body.removeChild(anchorElement); anchorElement.download = resolvedFileName;
anchorElement.style.display = 'none';
// 撤销临时的文件 URL // 将 <a> 元素添加到 body 中并触发下载
window.URL.revokeObjectURL(fileUrl); document.body.appendChild(anchorElement);
}); anchorElement.click();
}) document.body.removeChild(anchorElement);
.catch(error => {
console.error('DownFile error ', error); // 撤销临时的文件 URL
}); window.URL.revokeObjectURL(fileUrl);
return true;
} catch (error) {
console.error('DownFile error ', error);
throw error;
}
} }
//下载文件 //下载文件
export function postJson_downloadFile(url, fileName, jsonBody) { export async function postJson_downloadFile(url, fileName, jsonBody) {
const params = new URLSearchParams();
try {
// 发起 fetch 请求 const response = await fetch(url, {
fetch(url, { method: 'POST',
method: 'POST', headers: {
headers: { 'Content-Type': 'application/json'
'Content-Type': 'application/json' },
}, body: jsonBody
body: jsonBody
})
.then(response => {
// 获取响应头中的 content-disposition
const dispositionHeader = response.headers.get('content-disposition');
let resolvedFileName = fileName;
if (dispositionHeader) {
// 解析出文件名
const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(dispositionHeader);
const serverFileName = match && match[1] ? match[1].replace(/['"]/g, '') : null;
if (serverFileName) {
resolvedFileName = serverFileName;
}
}
// 将响应转换为 blob 对象
return response.blob().then(blob => {
// 创建临时的文件 URL
const fileUrl = window.URL.createObjectURL(blob);
// 创建一个 <a> 元素并设置下载链接和文件名
const anchorElement = document.createElement('a');
anchorElement.href = fileUrl;
anchorElement.download = resolvedFileName;
anchorElement.style.display = 'none';
// 将 <a> 元素添加到 body 中并触发下载
document.body.appendChild(anchorElement);
anchorElement.click();
document.body.removeChild(anchorElement);
// 撤销临时的文件 URL
window.URL.revokeObjectURL(fileUrl);
});
})
.catch(error => {
console.error('downfile error ', error);
}); });
const dispositionHeader = response.headers.get('content-disposition');
let resolvedFileName = fileName;
if (dispositionHeader) {
const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(dispositionHeader);
const serverFileName = match && match[1] ? match[1].replace(/['"]/g, '') : null;
if (serverFileName) {
resolvedFileName = serverFileName;
}
}
const blob = await response.blob();
const fileUrl = window.URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = fileUrl;
anchorElement.download = resolvedFileName;
anchorElement.style.display = 'none';
document.body.appendChild(anchorElement);
anchorElement.click();
document.body.removeChild(anchorElement);
window.URL.revokeObjectURL(fileUrl);
return true; // 唯一新增的返回值
} catch (error) {
console.error('downfile error ', error);
}
} }

View File

@@ -1,9 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<PluginVersion>10.6.16</PluginVersion> <PluginVersion>10.6.31</PluginVersion>
<ProPluginVersion>10.6.16</ProPluginVersion> <ProPluginVersion>10.6.31</ProPluginVersion>
<AuthenticationVersion>2.1.7</AuthenticationVersion> <AuthenticationVersion>2.1.8</AuthenticationVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -61,14 +61,14 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
{ {
return;//此处可判断,如果为服务器,则不用使用心跳。 return;//此处可判断,如果为服务器,则不用使用心跳。
} }
if (HeartbeatTime > 0)
SendHeartbeat = true;
HeartbeatTime = Math.Max(HeartbeatTime, 1000); HeartbeatTime = Math.Max(HeartbeatTime, 1000);
if (DtuId.IsNullOrWhiteSpace()) return; if (DtuId.IsNullOrWhiteSpace()) return;
if (client is ITcpClient tcpClient) if (client is ITcpClient tcpClient)
{ {
SendHeartbeat = true;
await tcpClient.SendAsync(DtuIdByte).ConfigureAwait(false); await tcpClient.SendAsync(DtuIdByte).ConfigureAwait(false);
if (Task == null) if (Task == null)

View File

@@ -10,8 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.5" />
<PackageReference Include="TouchSocket" Version="3.1.4" /> <PackageReference Include="TouchSocket" Version="3.1.5" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.4" /> <PackageReference Include="TouchSocket.SerialPorts" Version="3.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -35,12 +35,14 @@ namespace ThingsGateway.Gateway.Application;
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)] [ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
[Route("openApi/control")] [Route("openApi/control")]
[RolePermission] [RolePermission]
[LoggingMonitor] [RequestAudit]
[ApiController] [ApiController]
[Authorize(AuthenticationSchemes = "Bearer")] [Authorize(AuthenticationSchemes = "Bearer")]
public class ControlController : ControllerBase public class ControlController : ControllerBase
{ {
/// <summary> /// <summary>
/// 清空全部缓存 /// 清空全部缓存
/// </summary> /// </summary>
@@ -175,8 +177,50 @@ public class ControlController : ControllerBase
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables.Adapt<List<Variable>>(), type, restart, default); return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables.Adapt<List<Variable>>(), type, restart, default);
} }
/// <summary>
/// 删除通道
/// </summary>
[HttpPost("deleteChannel")]
[DisplayName("删除通道")]
public Task<bool> DeleteChannelAsync([FromBody] List<long> ids, bool restart)
{
if (ids == null || ids.Count == 0) ids = GlobalData.Channels.Keys.ToList();
return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default);
}
/// <summary>
/// 删除设备
/// </summary>
[HttpPost("deleteDevice")]
[DisplayName("删除设备")]
public Task<bool> DeleteDeviceAsync([FromBody] List<long> ids, bool restart)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdDevices.Keys.ToList();
return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart, default);
}
/// <summary>
/// 删除变量
/// </summary>
[HttpPost("deleteVariable")]
[DisplayName("删除变量")]
public Task<bool> DeleteVariableAsync([FromBody] List<long> ids, bool restart)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdVariables.Keys.ToList();
return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart, default);
}
/// <summary>
/// 增加测试数据
/// </summary>
[HttpPost("insertTestData")]
[DisplayName("增加测试数据")]
public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart)
{
return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart, default);
}
} }
public class ChannelInput public class ChannelInput
{ {

View File

@@ -90,6 +90,7 @@ public class RuntimeInfoController : ControllerBase
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpPost("checkRealAlarm")] [HttpPost("checkRealAlarm")]
[RequestAudit]
[DisplayName("确认实时报警")] [DisplayName("确认实时报警")]
public async Task CheckRealAlarm(long variableId) public async Task CheckRealAlarm(long variableId)
{ {

View File

@@ -326,11 +326,14 @@ public abstract class DriverBase : DisposableObject, IDriver
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
TextLogger?.Dispose();
_logger?.TryDispose();
IdVariableRuntimes?.Clear(); IdVariableRuntimes?.Clear();
IdVariableRuntimes = null; IdVariableRuntimes = null;
var device = CurrentDevice; var device = CurrentDevice;
if (device != null) if (device != null)
device.Driver = null; device.Driver = null;
LogMessage?.Logs?.ForEach(a => a.TryDispose()); LogMessage?.Logs?.ForEach(a => a.TryDispose());
LogMessage = null; LogMessage = null;
pluginPropertyEditorItems?.Clear(); pluginPropertyEditorItems?.Clear();

View File

@@ -0,0 +1,464 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections;
namespace ThingsGateway.Gateway.Application;
public class ThreadSafeStringDictionary<T> : IDictionary<string, T>, IReadOnlyDictionary<string, T>
{
private const int DEFAULT_PARTITIONS = 128;
private readonly Dictionary<string, T>[] _partitions;
private readonly object[] _partitionLocks;
private readonly IEqualityComparer<string> _comparer;
public ThreadSafeStringDictionary() : this(DEFAULT_PARTITIONS, null) { }
public ThreadSafeStringDictionary(int partitionCount, IEqualityComparer<string> comparer)
{
if (partitionCount < 1)
throw new ArgumentOutOfRangeException(nameof(partitionCount));
_partitions = new Dictionary<string, T>[partitionCount];
_partitionLocks = new object[partitionCount];
_comparer = comparer ?? StringComparer.Ordinal;
for (int i = 0; i < partitionCount; i++)
{
_partitions[i] = new Dictionary<string, T>(_comparer);
_partitionLocks[i] = new object();
}
}
private int GetPartitionIndex(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return Math.Abs(_comparer.GetHashCode(key)) % _partitions.Length;
}
// 基本操作
public T this[string key]
{
get
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index][key];
}
}
set
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
_partitions[index][key] = value;
}
}
}
public ICollection<string> Keys => GetAllItems().Select(kv => kv.Key).ToList();
public ICollection<T> Values => GetAllItems().Select(kv => kv.Value).ToList();
public int Count
{
get
{
int count = 0;
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
count += _partitions[i].Count;
}
}
return count;
}
}
public bool IsReadOnly => false;
IEnumerable<string> IReadOnlyDictionary<string, T>.Keys => Keys;
IEnumerable<T> IReadOnlyDictionary<string, T>.Values => Values;
public void Add(string key, T value)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
_partitions[index].Add(key, value);
}
}
public void Add(KeyValuePair<string, T> item) => Add(item.Key, item.Value);
public void Clear()
{
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
_partitions[i].Clear();
}
}
}
public bool Contains(KeyValuePair<string, T> item)
{
int index = GetPartitionIndex(item.Key);
lock (_partitionLocks[index])
{
return _partitions[index].TryGetValue(item.Key, out var value) &&
EqualityComparer<T>.Default.Equals(value, item.Value);
}
}
public bool ContainsKey(string key)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index].ContainsKey(key);
}
}
public bool Remove(string key)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index].Remove(key);
}
}
public bool Remove(KeyValuePair<string, T> item)
{
int index = GetPartitionIndex(item.Key);
lock (_partitionLocks[index])
{
if (_partitions[index].TryGetValue(item.Key, out var value) &&
EqualityComparer<T>.Default.Equals(value, item.Value))
{
return _partitions[index].Remove(item.Key);
}
return false;
}
}
public bool TryGetValue(string key, out T value)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index].TryGetValue(key, out value);
}
}
public void CopyTo(KeyValuePair<string, T>[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (array.Length - arrayIndex < Count) throw new ArgumentException("Target array too small");
foreach (var item in GetAllItems())
{
array[arrayIndex++] = item;
}
}
// 枚举器实现
public IEnumerator<KeyValuePair<string, T>> GetEnumerator()
{
return GetAllItems().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void AddRange(IEnumerable<KeyValuePair<string, T>> items)
{
var grouped = items.GroupBy(item => GetPartitionIndex(item.Key));
foreach (var group in grouped)
{
lock (_partitionLocks[group.Key])
{
foreach (var item in group)
{
_partitions[group.Key][item.Key] = item.Value;
}
}
}
}
public Dictionary<string, T> GetSnapshot()
{
var snapshot = new Dictionary<string, T>(_comparer);
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
foreach (var kvp in _partitions[i])
{
snapshot[kvp.Key] = kvp.Value;
}
}
}
return snapshot;
}
private IEnumerable<KeyValuePair<string, T>> GetAllItems()
{
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
foreach (var item in _partitions[i]) // 直接枚举原字典
{
yield return item;
}
}
}
}
}
public class ThreadSafeLongDictionary<T> : IDictionary<long, T>, IReadOnlyDictionary<long, T>
{
private const int DEFAULT_PARTITIONS = 128;
private readonly Dictionary<long, T>[] _partitions;
private readonly object[] _partitionLocks;
public ThreadSafeLongDictionary() : this(DEFAULT_PARTITIONS) { }
public ThreadSafeLongDictionary(int partitionCount)
{
if (partitionCount < 1)
throw new ArgumentOutOfRangeException(nameof(partitionCount));
_partitions = new Dictionary<long, T>[partitionCount];
_partitionLocks = new object[partitionCount];
for (int i = 0; i < partitionCount; i++)
{
_partitions[i] = new Dictionary<long, T>();
_partitionLocks[i] = new object();
}
}
private int GetPartitionIndex(long key)
{
// 使用混合哈希算法减少碰撞
uint hash = (uint)key;
hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
hash = (hash >> 16) ^ hash;
return (int)(hash % _partitions.Length);
}
public T this[long key]
{
get
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index][key];
}
}
set
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
_partitions[index][key] = value;
}
}
}
public ICollection<long> Keys => GetAllItems().Select(kv => kv.Key).ToList();
public ICollection<T> Values => GetAllItems().Select(kv => kv.Value).ToList();
public int Count
{
get
{
int count = 0;
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
count += _partitions[i].Count;
}
}
return count;
}
}
public bool IsReadOnly => false;
IEnumerable<long> IReadOnlyDictionary<long, T>.Keys => Keys;
IEnumerable<T> IReadOnlyDictionary<long, T>.Values => Values;
public void Add(long key, T value)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
_partitions[index].Add(key, value);
}
}
public void Add(KeyValuePair<long, T> item) => Add(item.Key, item.Value);
public void Clear()
{
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
_partitions[i].Clear();
}
}
}
public bool Contains(KeyValuePair<long, T> item)
{
int index = GetPartitionIndex(item.Key);
lock (_partitionLocks[index])
{
return _partitions[index].TryGetValue(item.Key, out var value) &&
EqualityComparer<T>.Default.Equals(value, item.Value);
}
}
public bool ContainsKey(long key)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index].ContainsKey(key);
}
}
public bool Remove(long key)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index].Remove(key);
}
}
public bool Remove(KeyValuePair<long, T> item)
{
int index = GetPartitionIndex(item.Key);
lock (_partitionLocks[index])
{
if (_partitions[index].TryGetValue(item.Key, out var value) &&
EqualityComparer<T>.Default.Equals(value, item.Value))
{
return _partitions[index].Remove(item.Key);
}
return false;
}
}
public bool TryGetValue(long key, out T value)
{
int index = GetPartitionIndex(key);
lock (_partitionLocks[index])
{
return _partitions[index].TryGetValue(key, out value);
}
}
public void CopyTo(KeyValuePair<long, T>[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (array.Length - arrayIndex < Count) throw new ArgumentException("Target array too small");
foreach (var item in GetAllItems())
{
array[arrayIndex++] = item;
}
}
public IEnumerator<KeyValuePair<long, T>> GetEnumerator()
{
return GetAllItems().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void AddRange(IEnumerable<KeyValuePair<long, T>> items)
{
var grouped = items.GroupBy(item => GetPartitionIndex(item.Key));
foreach (var group in grouped)
{
lock (_partitionLocks[group.Key])
{
foreach (var item in group)
{
_partitions[group.Key][item.Key] = item.Value;
}
}
}
}
public Dictionary<long, T> GetSnapshot()
{
var snapshot = new Dictionary<long, T>();
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
foreach (var kvp in _partitions[i])
{
snapshot[kvp.Key] = kvp.Value;
}
}
}
return snapshot;
}
private IEnumerable<KeyValuePair<long, T>> GetAllItems()
{
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
foreach (var item in _partitions[i])
{
yield return item;
}
}
}
}
public string GetPartitionStats()
{
var stats = new System.Text.StringBuilder();
for (int i = 0; i < _partitions.Length; i++)
{
lock (_partitionLocks[i])
{
stats.AppendLine($"Partition {i}: {_partitions[i].Count} items");
}
}
return stats.ToString();
}
}

View File

@@ -80,7 +80,14 @@
"WriteVariablesAsync": "Write variables", "WriteVariablesAsync": "Write variables",
"RemoveAllCache": "Remove all cache", "RemoveAllCache": "Remove all cache",
"RemoveCache": "Remove device/channel Cache", "RemoveCache": "Remove device/channel Cache",
"RestartAllThread": "Restart all thread" "RestartAllThread": "Restart all thread",
"BatchSaveChannelAsync": "BatchSaveChannel",
"BatchSaveDeviceAsync": "BatchSaveDevice",
"BatchSaveVariableAsync": "BatchSaveVariable",
"DeleteChannelAsync": "DeleteChannel",
"DeleteDeviceAsync": "DeleteDevice",
"DeleteVariableAsync": "DeleteVariable",
"InsertTestDataAsync": "InsertTestData"
}, },
"ThingsGateway.Gateway.Application.RuntimeInfoController": { "ThingsGateway.Gateway.Application.RuntimeInfoController": {
"RuntimeInfoController": "Get runtime information", "RuntimeInfoController": "Get runtime information",

View File

@@ -78,7 +78,14 @@
"PauseDeviceThreadAsync": "控制设备线程启停", "PauseDeviceThreadAsync": "控制设备线程启停",
"RestartAllThread": "重启全部线程", "RestartAllThread": "重启全部线程",
"RestartDeviceThreadAsync": "重启设备线程", "RestartDeviceThreadAsync": "重启设备线程",
"WriteVariablesAsync": "写入变量" "WriteVariablesAsync": "写入变量",
"BatchSaveChannelAsync": "保存通道",
"BatchSaveDeviceAsync": "保存设备",
"BatchSaveVariableAsync": "保存变量",
"DeleteChannelAsync": "删除通道",
"DeleteDeviceAsync": "删除设备",
"DeleteVariableAsync": "删除变量",
"InsertTestDataAsync": "增加测试数据"
}, },
"ThingsGateway.Gateway.Application.RuntimeInfoController": { "ThingsGateway.Gateway.Application.RuntimeInfoController": {
"RuntimeInfoController": "获取运行态信息", "RuntimeInfoController": "获取运行态信息",

View File

@@ -17,6 +17,7 @@ using Newtonsoft.Json.Linq;
using SqlSugar; using SqlSugar;
using ThingsGateway.Gateway.Application.Extensions; using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Json.Extension; using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Application; namespace ThingsGateway.Gateway.Application;
@@ -34,10 +35,6 @@ public class VariableRuntime : Variable, IVariable, IDisposable
private bool? _isOnlineChanged; private bool? _isOnlineChanged;
protected object? _value; protected object? _value;
public VariableRuntime()
{
}
/// <summary> /// <summary>
/// 变化时间 /// 变化时间
/// </summary> /// </summary>
@@ -343,7 +340,7 @@ public class VariableRuntime : Variable, IVariable, IDisposable
public void Init(DeviceRuntime deviceRuntime) public void Init(DeviceRuntime deviceRuntime)
{ {
GlobalData.AlarmEnableIdVariables.TryRemove(Id, out _); GlobalData.AlarmEnableIdVariables.Remove(Id);
if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm)) if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
{ {
oldAlarm.EventType = EventTypeEnum.Finish; oldAlarm.EventType = EventTypeEnum.Finish;
@@ -352,12 +349,12 @@ public class VariableRuntime : Variable, IVariable, IDisposable
} }
DeviceRuntime?.VariableRuntimes?.TryRemove(Name, out _); DeviceRuntime?.VariableRuntimes?.Remove(Name);
DeviceRuntime = deviceRuntime; DeviceRuntime = deviceRuntime;
DeviceRuntime?.VariableRuntimes?.TryAdd(Name, this); DeviceRuntime?.VariableRuntimes?.TryAdd(Name, this);
GlobalData.IdVariables.TryRemove(Id, out _); GlobalData.IdVariables.Remove(Id);
GlobalData.IdVariables.TryAdd(Id, this); GlobalData.IdVariables.TryAdd(Id, this);
if (AlarmEnable) if (AlarmEnable)
{ {
@@ -369,11 +366,11 @@ public class VariableRuntime : Variable, IVariable, IDisposable
public void Dispose() public void Dispose()
{ {
DeviceRuntime?.VariableRuntimes?.TryRemove(Name, out _); DeviceRuntime?.VariableRuntimes?.Remove(Name);
GlobalData.IdVariables.TryRemove(Id, out _); GlobalData.IdVariables.Remove(Id);
GlobalData.AlarmEnableIdVariables.TryRemove(Id, out _); GlobalData.AlarmEnableIdVariables.Remove(Id);
if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm)) if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
{ {
oldAlarm.EventType = EventTypeEnum.Finish; oldAlarm.EventType = EventTypeEnum.Finish;

View File

@@ -26,28 +26,53 @@ internal sealed class GatewayExportService : IGatewayExportService
private IJSRuntime JSRuntime { get; set; } private IJSRuntime JSRuntime { get; set; }
public async Task OnChannelExport(ExportFilter exportFilter) public async Task<bool> OnChannelExport(ExportFilter exportFilter)
{ {
await using var ajaxJS = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Razor/js/downloadFile.js"); try
string url = "api/gatewayExport/channel"; {
string fileName = $"{DateTime.Now.ToFileDateTimeFormat()}.xlsx";
await ajaxJS.InvokeVoidAsync("postJson_downloadFile", url, fileName, exportFilter.ToJsonString()); await using var ajaxJS = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Razor/js/downloadFile.js");
string url = "api/gatewayExport/channel";
string fileName = $"{DateTime.Now.ToFileDateTimeFormat()}.xlsx";
return await ajaxJS.InvokeAsync<bool>("postJson_downloadFile", url, fileName, exportFilter.ToJsonString());
}
catch
{
return false;
}
} }
public async Task OnDeviceExport(ExportFilter exportFilter) public async Task<bool> OnDeviceExport(ExportFilter exportFilter)
{ {
await using var ajaxJS = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Razor/js/downloadFile.js"); try
string url = "api/gatewayExport/device"; {
string fileName = $"{DateTime.Now.ToFileDateTimeFormat()}.xlsx";
await ajaxJS.InvokeVoidAsync("postJson_downloadFile", url, fileName, exportFilter.ToJsonString());
await using var ajaxJS = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Razor/js/downloadFile.js");
string url = "api/gatewayExport/device";
string fileName = $"{DateTime.Now.ToFileDateTimeFormat()}.xlsx";
return await ajaxJS.InvokeAsync<bool>("postJson_downloadFile", url, fileName, exportFilter.ToJsonString());
}
catch
{
return false;
}
} }
public async Task OnVariableExport(ExportFilter exportFilter) public async Task<bool> OnVariableExport(ExportFilter exportFilter)
{ {
await using var ajaxJS = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Razor/js/downloadFile.js"); try
string url = "api/gatewayExport/variable"; {
string fileName = $"{DateTime.Now.ToFileDateTimeFormat()}.xlsx"; await using var ajaxJS = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Razor/js/downloadFile.js");
await ajaxJS.InvokeVoidAsync("postJson_downloadFile", url, fileName, exportFilter.ToJsonString()); string url = "api/gatewayExport/variable";
string fileName = $"{DateTime.Now.ToFileDateTimeFormat()}.xlsx";
return await ajaxJS.InvokeAsync<bool>("postJson_downloadFile", url, fileName, exportFilter.ToJsonString());
}
catch
{
return false;
}
} }
} }

View File

@@ -35,18 +35,30 @@ public sealed class HybridGatewayExportService : IGatewayExportService
} }
public async Task OnChannelExport(ExportFilter exportFilter) public async Task<bool> OnChannelExport(ExportFilter exportFilter)
{ {
exportFilter.QueryPageOptions.IsPage = false; try
exportFilter.QueryPageOptions.IsVirtualScroll = false; {
var sheets = await _channelService.ExportChannelAsync(exportFilter).ConfigureAwait(false);
var path = await _importExportService.CreateFileAsync<Device>(sheets, "Channel", false).ConfigureAwait(false);
Open(path); exportFilter.QueryPageOptions.IsPage = false;
exportFilter.QueryPageOptions.IsVirtualScroll = false;
var sheets = await _channelService.ExportChannelAsync(exportFilter).ConfigureAwait(false);
var path = await _importExportService.CreateFileAsync<Device>(sheets, "Channel", false).ConfigureAwait(false);
Open(path);
return true;
}
catch
{
return false;
}
} }
private static void Open(string path) private static bool Open(string path)
{ {
path = System.IO.Path.GetDirectoryName(path); // Ensure the path is absolute path = System.IO.Path.GetDirectoryName(path); // Ensure the path is absolute
@@ -63,25 +75,47 @@ public sealed class HybridGatewayExportService : IGatewayExportService
{ {
System.Diagnostics.Process.Start("open", path); System.Diagnostics.Process.Start("open", path);
} }
return true;
} }
public async Task OnDeviceExport(ExportFilter exportFilter) public async Task<bool> OnDeviceExport(ExportFilter exportFilter)
{ {
exportFilter.QueryPageOptions.IsPage = false; try
exportFilter.QueryPageOptions.IsVirtualScroll = false; {
var sheets = await _deviceService.ExportDeviceAsync(exportFilter).ConfigureAwait(false);
var path = await _importExportService.CreateFileAsync<Device>(sheets, "Device", false).ConfigureAwait(false);
Open(path);
exportFilter.QueryPageOptions.IsPage = false;
exportFilter.QueryPageOptions.IsVirtualScroll = false;
var sheets = await _deviceService.ExportDeviceAsync(exportFilter).ConfigureAwait(false);
var path = await _importExportService.CreateFileAsync<Device>(sheets, "Device", false).ConfigureAwait(false);
Open(path);
return true;
}
catch
{
return false;
}
} }
public async Task OnVariableExport(ExportFilter exportFilter) public async Task<bool> OnVariableExport(ExportFilter exportFilter)
{ {
exportFilter.QueryPageOptions.IsPage = false; try
exportFilter.QueryPageOptions.IsVirtualScroll = false; {
var sheets = await _variableService.ExportVariableAsync(exportFilter).ConfigureAwait(false);
var path = await _importExportService.CreateFileAsync<Variable>(sheets, "Variable", false).ConfigureAwait(false); exportFilter.QueryPageOptions.IsPage = false;
Open(path); exportFilter.QueryPageOptions.IsVirtualScroll = false;
var sheets = await _variableService.ExportVariableAsync(exportFilter).ConfigureAwait(false);
var path = await _importExportService.CreateFileAsync<Variable>(sheets, "Variable", false).ConfigureAwait(false);
Open(path);
return true;
}
catch
{
return false;
}
} }
} }

View File

@@ -12,7 +12,7 @@ namespace ThingsGateway.Gateway.Application;
public interface IGatewayExportService public interface IGatewayExportService
{ {
Task OnChannelExport(ExportFilter exportFilter); Task<bool> OnChannelExport(ExportFilter exportFilter);
Task OnDeviceExport(ExportFilter exportFilter); Task<bool> OnDeviceExport(ExportFilter exportFilter);
Task OnVariableExport(ExportFilter exportFilter); Task<bool> OnVariableExport(ExportFilter exportFilter);
} }

View File

@@ -96,6 +96,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
} }
#endregion #endregion
Microsoft.Extensions.Logging.ILogger? _logger;
/// <summary> /// <summary>
/// 通道线程构造函数,用于初始化通道线程实例。 /// 通道线程构造函数,用于初始化通道线程实例。
@@ -113,9 +114,9 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
// 设置通道信息 // 设置通道信息
CurrentChannel = channelRuntime; CurrentChannel = channelRuntime;
var logger = App.RootServices.GetService<Microsoft.Extensions.Logging.ILoggerFactory>().CreateLogger($"DeviceThreadManage[{channelRuntime.Name}]"); _logger = App.RootServices.GetService<Microsoft.Extensions.Logging.ILoggerFactory>().CreateLogger($"DeviceThreadManage[{channelRuntime.Name}]");
// 添加默认日志记录器 // 添加默认日志记录器
LogMessage.AddLogger(new EasyLogger(logger.Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace }); LogMessage.AddLogger(new EasyLogger(_logger.Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
// 根据配置获取通道实例 // 根据配置获取通道实例
Channel = channelRuntime.GetChannel(config); Channel = channelRuntime.GetChannel(config);
@@ -898,7 +899,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
try try
{ {
await NewDeviceLock.WaitAsync().ConfigureAwait(false); await NewDeviceLock.WaitAsync().ConfigureAwait(false);
_logger?.TryDispose();
await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false); await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
if (Channel?.Collects.Count == 0) if (Channel?.Collects.Count == 0)
Channel?.SafeDispose(); Channel?.SafeDispose();

View File

@@ -518,7 +518,7 @@ internal sealed class PluginService : IPluginService
{ {
var fileInfo = new FileInfo(path); var fileInfo = new FileInfo(path);
if (fileInfo.Exists) if (fileInfo.Exists)
fileInfo.MoveTo($"{path}{YitIdHelper.NextId()}{DelEx}", true); fileInfo.MoveTo($"{path}{CommonUtils.GetSingleId()}{DelEx}", true);
else else
return false; return false;
return true; return true;

View File

@@ -18,6 +18,8 @@ using SqlSugar;
using System.Reflection; using System.Reflection;
using ThingsGateway.Authentication;
namespace ThingsGateway.Gateway.Application; namespace ThingsGateway.Gateway.Application;
[AppStartup(-100)] [AppStartup(-100)]
@@ -25,6 +27,9 @@ public class Startup : AppStartup
{ {
public void Configure(IServiceCollection services) public void Configure(IServiceCollection services)
{ {
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
services.AddConfigurableOptions<ChannelThreadOptions>(); services.AddConfigurableOptions<ChannelThreadOptions>();
services.AddConfigurableOptions<GatewayLogOptions>(); services.AddConfigurableOptions<GatewayLogOptions>();
services.AddConfigurableOptions<RpcLogOptions>(); services.AddConfigurableOptions<RpcLogOptions>();
@@ -125,8 +130,35 @@ public class Startup : AppStartup
} }
catch { } catch { }
try
{
using var db = DbContext.GetDB<BackendLog>();
if (db.CurrentConnectionConfig.DbType == SqlSugar.DbType.Sqlite)
{
if (!db.DbMaintenance.IsAnyIndex("idx_backendlog_logtime_date"))
{
var indexsql = "CREATE INDEX idx_backendlog_logtime_date ON backend_log(strftime('%Y-%m-%d', LogTime));";
db.Ado.ExecuteCommand(indexsql);
}
}
}
catch { }
try
{
using var db = DbContext.GetDB<RpcLog>();
if (db.CurrentConnectionConfig.DbType == SqlSugar.DbType.Sqlite)
{
if (!db.DbMaintenance.IsAnyIndex("idx_rpclog_logtime_date"))
{
var indexsql = "CREATE INDEX idx_rpclog_logtime_date ON rpc_log(strftime('%Y-%m-%d', LogTime));";
db.Ado.ExecuteCommand(indexsql);
}
}
}
catch { }
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStarted.Register(() => serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStarted.Register(() =>
{ {

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)Version.props" /> <Import Project="$(SolutionDir)Version.props" />
<Import Project="$(SolutionDir)PackNuget.props" /> <Import Project="$(SolutionDir)PackNuget.props" />
@@ -8,8 +8,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> <PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" /> <PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.4" /> <PackageReference Include="TouchSocket.Dmtp" Version="3.1.5" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.4" /> <PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.5" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" /> <PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
</ItemGroup> </ItemGroup>

View File

@@ -10,6 +10,7 @@
using Mapster; using Mapster;
using ThingsGateway.Admin.Application;
using ThingsGateway.Gateway.Application; using ThingsGateway.Gateway.Application;
using Yitter.IdGenerator; using Yitter.IdGenerator;
@@ -54,14 +55,14 @@ public partial class ChannelCopyComponent
for (int i = 0; i < CopyCount; i++) for (int i = 0; i < CopyCount; i++)
{ {
Channel channel = Model.Adapt<Channel>(); Channel channel = Model.Adapt<Channel>();
channel.Id = YitIdHelper.NextId(); channel.Id = CommonUtils.GetSingleId();
channel.Name = $"{CopyChannelNamePrefix}{CopyChannelNameSuffixNumber + i}"; channel.Name = $"{CopyChannelNamePrefix}{CopyChannelNameSuffixNumber + i}";
int index = 0; int index = 0;
foreach (var item in Devices) foreach (var item in Devices)
{ {
Device device = item.Key.Adapt<Device>(); Device device = item.Key.Adapt<Device>();
device.Id = YitIdHelper.NextId(); device.Id = CommonUtils.GetSingleId();
device.Name = $"{channel.Name}_{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + (index++)}"; device.Name = $"{channel.Name}_{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + (index++)}";
device.ChannelId = channel.Id; device.ChannelId = channel.Id;
List<Variable> variables = new(); List<Variable> variables = new();
@@ -69,7 +70,7 @@ public partial class ChannelCopyComponent
foreach (var variable in item.Value) foreach (var variable in item.Value)
{ {
Variable v = variable.Adapt<Variable>(); Variable v = variable.Adapt<Variable>();
v.Id = YitIdHelper.NextId(); v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id; v.DeviceId = device.Id;
variables.Add(v); variables.Add(v);
} }

View File

@@ -220,9 +220,10 @@ public partial class ChannelTable : IDisposable
private async Task ExcelExportAsync(ITableExportContext<ChannelRuntime> tableExportContext, bool all = false) private async Task ExcelExportAsync(ITableExportContext<ChannelRuntime> tableExportContext, bool all = false)
{ {
bool ret;
if (all) if (all)
{ {
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() });
} }
else else
{ {
@@ -230,16 +231,16 @@ public partial class ChannelTable : IDisposable
{ {
case ChannelDevicePluginTypeEnum.PluginName: case ChannelDevicePluginTypeEnum.PluginName:
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName });
break; break;
case ChannelDevicePluginTypeEnum.Channel: case ChannelDevicePluginTypeEnum.Channel:
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id });
break; break;
case ChannelDevicePluginTypeEnum.Device: case ChannelDevicePluginTypeEnum.Device:
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType });
break; break;
default: default:
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() });
break; break;
} }
@@ -247,7 +248,8 @@ public partial class ChannelTable : IDisposable
} }
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
async Task ExcelChannelAsync(ITableExportContext<ChannelRuntime> tableExportContext) async Task ExcelChannelAsync(ITableExportContext<ChannelRuntime> tableExportContext)

View File

@@ -530,20 +530,21 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
} }
async Task ExportCurrentChannel(ContextMenuItem item, object value) async Task ExportCurrentChannel(ContextMenuItem item, object value)
{ {
bool ret;
if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return; if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return;
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime)) if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{ {
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), DeviceId = channelRuntime.Id }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), DeviceId = channelRuntime.Id });
} }
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName)) else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
{ {
//插件名称 //插件名称
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), PluginName = pluginName }); ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new(), PluginName = pluginName });
} }
else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType)) else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType))
{ {
await GatewayExportService.OnChannelExport(new ExportFilter() { QueryPageOptions = new(), PluginType = pluginType }); ret = await GatewayExportService.OnChannelExport(new ExportFilter() { QueryPageOptions = new(), PluginType = pluginType });
} }
else else
{ {
@@ -551,14 +552,17 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
} }
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
async Task ExportAllChannel(ContextMenuItem item, object value) async Task ExportAllChannel(ContextMenuItem item, object value)
{ {
await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() }); bool ret;
ret = await GatewayExportService.OnChannelExport(new() { QueryPageOptions = new() });
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
@@ -1065,25 +1069,26 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
async Task ExportCurrentDevice(ContextMenuItem item, object value) async Task ExportCurrentDevice(ContextMenuItem item, object value)
{ {
bool ret;
if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return; if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return;
if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime)) if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{ {
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), DeviceId = deviceRuntime.Id }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), DeviceId = deviceRuntime.Id });
} }
else if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime)) else if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{ {
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), ChannelId = channelRuntime.Id }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), ChannelId = channelRuntime.Id });
} }
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName)) else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
{ {
//插件名称 //插件名称
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginName = pluginName }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginName = pluginName });
} }
else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType)) else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType))
{ {
//采集 //采集
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginType = pluginType }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginType = pluginType });
} }
else else
{ {
@@ -1091,14 +1096,17 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
} }
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
async Task ExportAllDevice(ContextMenuItem item, object value) async Task ExportAllDevice(ContextMenuItem item, object value)
{ {
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() }); bool ret;
ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() });
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
async Task ImportDevice(ContextMenuItem item, object value) async Task ImportDevice(ContextMenuItem item, object value)
@@ -1299,7 +1307,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
try try
{ {
if (Disposed) return; if (Disposed) return;
await Task.Delay(1000);
await OnClickSearch(SearchText); await OnClickSearch(SearchText);
Value = GetValue(Value); Value = GetValue(Value);
@@ -1311,6 +1318,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
} }
finally finally
{ {
await Task.Delay(1000);
_isExecuting = false; _isExecuting = false;
} }
} }

View File

@@ -10,6 +10,7 @@
using Mapster; using Mapster;
using ThingsGateway.Admin.Application;
using ThingsGateway.Gateway.Application; using ThingsGateway.Gateway.Application;
using Yitter.IdGenerator; using Yitter.IdGenerator;
@@ -50,14 +51,14 @@ public partial class DeviceCopyComponent
for (int i = 0; i < CopyCount; i++) for (int i = 0; i < CopyCount; i++)
{ {
Device device = Model.Adapt<Device>(); Device device = Model.Adapt<Device>();
device.Id = YitIdHelper.NextId(); device.Id = CommonUtils.GetSingleId();
device.Name = $"{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + i}"; device.Name = $"{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + i}";
List<Variable> variables = new(); List<Variable> variables = new();
foreach (var item in Variables) foreach (var item in Variables)
{ {
Variable v = item.Adapt<Variable>(); Variable v = item.Adapt<Variable>();
v.Id = YitIdHelper.NextId(); v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id; v.DeviceId = device.Id;
variables.Add(v); variables.Add(v);
} }

View File

@@ -221,9 +221,10 @@ public partial class DeviceTable : IDisposable
private async Task ExcelExportAsync(ITableExportContext<DeviceRuntime> tableExportContext, bool all = false) private async Task ExcelExportAsync(ITableExportContext<DeviceRuntime> tableExportContext, bool all = false)
{ {
bool ret;
if (all) if (all)
{ {
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() });
} }
else else
{ {
@@ -231,16 +232,16 @@ public partial class DeviceTable : IDisposable
{ {
case ChannelDevicePluginTypeEnum.PluginName: case ChannelDevicePluginTypeEnum.PluginName:
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName });
break; break;
case ChannelDevicePluginTypeEnum.Channel: case ChannelDevicePluginTypeEnum.Channel:
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id });
break; break;
case ChannelDevicePluginTypeEnum.Device: case ChannelDevicePluginTypeEnum.Device:
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType });
break; break;
default: default:
await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() }); ret = await GatewayExportService.OnDeviceExport(new() { QueryPageOptions = new() });
break; break;
} }
@@ -248,7 +249,8 @@ public partial class DeviceTable : IDisposable
} }
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
async Task ExcelDeviceAsync(ITableExportContext<DeviceRuntime> tableExportContext) async Task ExcelDeviceAsync(ITableExportContext<DeviceRuntime> tableExportContext)

View File

@@ -10,6 +10,7 @@
using Mapster; using Mapster;
using ThingsGateway.Admin.Application;
using ThingsGateway.Gateway.Application; using ThingsGateway.Gateway.Application;
using Yitter.IdGenerator; using Yitter.IdGenerator;
@@ -47,7 +48,7 @@ public partial class VariableCopyComponent
var variable = Model.Adapt<List<Variable>>(); var variable = Model.Adapt<List<Variable>>();
foreach (var item in variable) foreach (var item in variable)
{ {
item.Id = YitIdHelper.NextId(); item.Id = CommonUtils.GetSingleId();
item.Name = $"{CopyVariableNamePrefix}{CopyVariableNameSuffixNumber + i}"; item.Name = $"{CopyVariableNamePrefix}{CopyVariableNameSuffixNumber + i}";
variables.Add(item); variables.Add(item);
} }

View File

@@ -268,9 +268,10 @@ public partial class VariableRuntimeInfo : IDisposable
private async Task ExcelExportAsync(ITableExportContext<VariableRuntime> tableExportContext, bool all = false) private async Task ExcelExportAsync(ITableExportContext<VariableRuntime> tableExportContext, bool all = false)
{ {
bool ret;
if (all) if (all)
{ {
await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new() }); ret = await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new() });
} }
else else
{ {
@@ -278,16 +279,16 @@ public partial class VariableRuntimeInfo : IDisposable
{ {
case ChannelDevicePluginTypeEnum.PluginName: case ChannelDevicePluginTypeEnum.PluginName:
await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName }); ret = await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new(), PluginName = SelectModel.PluginName });
break; break;
case ChannelDevicePluginTypeEnum.Channel: case ChannelDevicePluginTypeEnum.Channel:
await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id }); ret = await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new(), ChannelId = SelectModel.ChannelRuntime.Id });
break; break;
case ChannelDevicePluginTypeEnum.Device: case ChannelDevicePluginTypeEnum.Device:
await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType }); ret = await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new(), DeviceId = SelectModel.DeviceRuntime.Id, PluginType = SelectModel.DeviceRuntime.PluginType });
break; break;
default: default:
await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new() }); ret = await GatewayExportService.OnVariableExport(new() { QueryPageOptions = new() });
break; break;
} }
@@ -295,7 +296,8 @@ public partial class VariableRuntimeInfo : IDisposable
} }
// 返回 true 时自动弹出提示框 // 返回 true 时自动弹出提示框
await ToastService.Default(); if (ret)
await ToastService.Default();
} }
async Task ExcelVariableAsync(ITableExportContext<VariableRuntime> tableExportContext) async Task ExcelVariableAsync(ITableExportContext<VariableRuntime> tableExportContext)

View File

@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" /> <ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.4" /> <PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> <PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.1" /> <PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.1" />
<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" /> <ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />

View File

@@ -18,7 +18,7 @@ namespace ThingsGateway.Management;
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)] [ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
[Route("openApi/autoUpdate")] [Route("openApi/autoUpdate")]
[RolePermission] [RolePermission]
[LoggingMonitor] [RequestAudit]
[ApiController] [ApiController]
[Authorize(AuthenticationSchemes = "Bearer")] [Authorize(AuthenticationSchemes = "Bearer")]
public class AutoUpdateController : ControllerBase public class AutoUpdateController : ControllerBase

View File

@@ -30,6 +30,7 @@ public partial class RulesPage
} }
protected override async ValueTask DisposeAsync(bool disposing) protected override async ValueTask DisposeAsync(bool disposing)
{ {
RulesDispatchService.UnSubscribe(Notify);
if (Module != null) if (Module != null)
{ {
await Module.InvokeVoidAsync("disposeJS", DiagramsJS); await Module.InvokeVoidAsync("disposeJS", DiagramsJS);
@@ -155,10 +156,6 @@ public partial class RulesPage
[NotNull] [NotNull]
private IDispatchService<Rules>? RulesDispatchService { get; set; } private IDispatchService<Rules>? RulesDispatchService { get; set; }
public void Dispose()
{
RulesDispatchService.UnSubscribe(Notify);
}
private ExecutionContext? context; private ExecutionContext? context;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()

View File

@@ -51,7 +51,7 @@ public class PackHelper
// 获取变量的位偏移量 // 获取变量的位偏移量
//if (item.DataType == DataTypeEnum.Boolean) //if (item.DataType == DataTypeEnum.Boolean)
item.Index = device.GetBitOffsetDefault(address); item.Index = device.GetBitOffsetDefault(address);
if (item.DataType == DataTypeEnum.Byte) if (item.DataType == DataTypeEnum.Byte&& !(item.ArrayLength>1))
item.Index += (item.Index % 2 == 0) ? 1 : -1; item.Index += (item.Index % 2 == 0) ? 1 : -1;
} }

View File

@@ -223,17 +223,21 @@ public partial class SiemensS7Master : DeviceBase
ushort dataLen = 0; ushort dataLen = 0;
ushort itemLen = 1; ushort itemLen = 1;
List<SiemensS7Address> addresses = new(); List<SiemensS7Address> addresses = new();
foreach (var item in sAddresss) for (int i = 0; i < sAddresss.Length; i++)
{ {
var item = sAddresss[i];
dataLen = (ushort)(dataLen + item.Data.Length + 4); dataLen = (ushort)(dataLen + item.Data.Length + 4);
ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen); ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen);
if (telegramLen < PduLength) if (telegramLen < PduLength)
{ {
addresses.Add(item); addresses.Add(item);
itemLen++; itemLen++;
if (i == sAddresss.Length - 1)
siemensS7Addresses.Add(addresses);
} }
else else
{ {
siemensS7Addresses.Add(addresses);
addresses = new(); addresses = new();
dataLen = 0; dataLen = 0;
itemLen = 1; itemLen = 1;
@@ -243,6 +247,8 @@ public partial class SiemensS7Master : DeviceBase
{ {
addresses.Add(item); addresses.Add(item);
itemLen++; itemLen++;
if (i == sAddresss.Length - 1)
siemensS7Addresses.Add(addresses);
} }
else else
{ {
@@ -250,7 +256,6 @@ public partial class SiemensS7Master : DeviceBase
return dictOperResult; return dictOperResult;
} }
} }
siemensS7Addresses.Add(addresses);
} }
@@ -278,6 +283,7 @@ public partial class SiemensS7Master : DeviceBase
} }
} }
} }
#region #region
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -19,7 +19,7 @@
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.31" GeneratePathProperty="true"> <PackageReference Include="SqlSugar.TDengineCore" Version="4.18.33" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> <PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference> </PackageReference>

View File

@@ -510,7 +510,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
{ {
if (GlobalData.ReadOnlyIdVariables.TryGetValue(item.Value.Id, out var variableRuntime)) if (GlobalData.ReadOnlyIdVariables.TryGetValue(item.Value.Id, out var variableRuntime))
{ {
writeInfos.Add((variableRuntime, hashSetNodeId[item.Key].Value.Value?.ToString())); writeInfos.Add((variableRuntime, hashSetNodeId[item.Key].Value.Value?.ToJsonString()));
} }
} }
@@ -635,7 +635,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
new() new()
{ {
{ {
variableRuntime.DeviceName, new Dictionary<string, string>() { {opcuaTag.SymbolicName, value?.ToString() } } variableRuntime.DeviceName, new Dictionary<string, string>() { {opcuaTag.SymbolicName, value?.ToJsonString() } }
} }
} }
).GetAwaiter().GetResult(); ).GetAwaiter().GetResult();

View File

@@ -357,7 +357,7 @@ public partial class OpcUaServer : BusinessBase
{ {
StoreType = CertificateStoreType.X509Store, StoreType = CertificateStoreType.X509Store,
StorePath = "CurrentUser\\UAServer_ThingsGateway", StorePath = "CurrentUser\\UAServer_ThingsGateway",
SubjectName = _driverPropertys.BigTextSubjectName, SubjectName = $"{_driverPropertys.BigTextSubjectName}{_driverPropertys.OpcUaStringUrl}",
//ValidationOptions = CertificateValidationOptions.SuppressHostNameInvalid, //ValidationOptions = CertificateValidationOptions.SuppressHostNameInvalid,
}, },

View File

@@ -12,6 +12,7 @@ using Mapster;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using ThingsGateway.Admin.Application;
using ThingsGateway.Extension.Generic; using ThingsGateway.Extension.Generic;
using ThingsGateway.Foundation; using ThingsGateway.Foundation;
@@ -21,6 +22,8 @@ using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc; using TouchSocket.Rpc;
using TouchSocket.Sockets; using TouchSocket.Sockets;
using Yitter.IdGenerator;
namespace ThingsGateway.Plugin.Synchronization; namespace ThingsGateway.Plugin.Synchronization;
@@ -60,11 +63,8 @@ public partial class Synchronization : BusinessBase, IRpcDriver
protected override async ValueTask ProtectedExecuteAsync(CancellationToken cancellationToken) protected override async ValueTask ProtectedExecuteAsync(CancellationToken cancellationToken)
{ {
try try
{ {
if (_driverPropertys.IsServer) if (_driverPropertys.IsServer)
{ {
if (_tcpDmtpService.ServerState != ServerState.Running) if (_tcpDmtpService.ServerState != ServerState.Running)
@@ -129,6 +129,7 @@ public partial class Synchronization : BusinessBase, IRpcDriver
// 如果 online 为 true表示设备在线 // 如果 online 为 true表示设备在线
if (online)
{ {
var deviceRunTimes = CollectDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).Adapt<List<DeviceDataWithValue>>(); var deviceRunTimes = CollectDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).Adapt<List<DeviceDataWithValue>>();
@@ -258,10 +259,7 @@ public partial class Synchronization : BusinessBase, IRpcDriver
var data = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>( var data = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(
nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false); nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
data.ForEach(a => a.Channel.Enable = false); await Add(data, cancellationToken).ConfigureAwait(false);
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
LogMessage?.LogTrace($"ForcedSync data success");
} }
} }
@@ -277,10 +275,10 @@ public partial class Synchronization : BusinessBase, IRpcDriver
foreach (var item in _tcpDmtpService.Clients) foreach (var item in _tcpDmtpService.Clients)
{ {
var data = await item.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false); var data = await item.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
data.ForEach(a => a.Channel.Enable = false);
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
LogMessage?.LogTrace($"{item.GetIPPort()}: ForcedSync data success"); await Add(data, cancellationToken).ConfigureAwait(false);
} }
} }
@@ -300,6 +298,27 @@ public partial class Synchronization : BusinessBase, IRpcDriver
} }
} }
private async Task Add(List<DataWithDatabase> data, CancellationToken cancellationToken)
{
data.ForEach(a =>
{
a.Channel.Enable = false;
a.Channel.Id = CommonUtils.GetSingleId();
a.DeviceVariables.ForEach(b =>
{
b.Device.ChannelId = a.Channel.Id;
b.Device.Id = CommonUtils.GetSingleId();
b.Variables.ForEach(c =>
{
c.DeviceId = b.Device.Id;
c.Id = 0;
});
});
});
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
LogMessage?.LogTrace($"ForcedSync data success");
}
/// <summary> /// <summary>
/// 异步写入方法 /// 异步写入方法
@@ -321,12 +340,12 @@ public partial class Synchronization : BusinessBase, IRpcDriver
{ {
if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas)) if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas))
{ {
variableDatas.Add(item.Key.Name, item.Value?.ToString() ?? string.Empty); variableDatas.TryAdd(item.Key.Name, item.Value?.ToString() ?? string.Empty);
} }
else else
{ {
deviceDatas.Add(item.Key.DeviceName ?? string.Empty, new()); deviceDatas.TryAdd(item.Key.DeviceName ?? string.Empty, new());
deviceDatas[item.Key.DeviceName ?? string.Empty].Add(item.Key.Name, item.Value?.ToString() ?? string.Empty); deviceDatas[item.Key.DeviceName ?? string.Empty].TryAdd(item.Key.Name, item.Value?.ToString() ?? string.Empty);
} }
} }
@@ -383,7 +402,7 @@ public partial class Synchronization : BusinessBase, IRpcDriver
try try
{ {
var data = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>( var data = await client.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
nameof(ReverseCallbackServer.Rpc), waitInvoke, new Dictionary<string, Dictionary<string, string>>() { { item.Key, item.Value } }).ConfigureAwait(false); nameof(ReverseCallbackServer.Rpc), waitInvoke, new Dictionary<string, Dictionary<string, string>>() { { item.Key, item.Value } }).ConfigureAwait(false);
dataResult.AddRange(data); dataResult.AddRange(data);
@@ -396,7 +415,7 @@ public partial class Synchronization : BusinessBase, IRpcDriver
foreach (var vItem in item.Value) foreach (var vItem in item.Value)
{ {
dataResult[item.Key].Add(vItem.Key, new OperResult<object>(ex)); dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>(ex));
} }
} }
} }
@@ -407,7 +426,7 @@ public partial class Synchronization : BusinessBase, IRpcDriver
foreach (var vItem in item.Value) foreach (var vItem in item.Value)
{ {
dataResult[item.Key].Add(vItem.Key, new OperResult<object>("No online")); dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
} }
} }

View File

@@ -15,7 +15,7 @@ namespace ThingsGateway.Plugin.Synchronization;
/// <summary> /// <summary>
/// <inheritdoc/> /// <inheritdoc/>
/// </summary> /// </summary>
public class SynchronizationProperty : BusinessPropertyBase public class SynchronizationProperty : BusinessPropertyBase, IBusinessPropertyAllVariableBase
{ {
[DynamicProperty] [DynamicProperty]
public bool IsServer { get; set; } = true; public bool IsServer { get; set; } = true;

View File

@@ -17,7 +17,6 @@ using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationM
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -37,7 +36,6 @@ using ThingsGateway.Admin.Razor;
using ThingsGateway.Debug; using ThingsGateway.Debug;
using ThingsGateway.Extension; using ThingsGateway.Extension;
using ThingsGateway.Gateway.Application; using ThingsGateway.Gateway.Application;
using ThingsGateway.Logging;
using ThingsGateway.NewLife.Caching; using ThingsGateway.NewLife.Caching;
namespace ThingsGateway.Server; namespace ThingsGateway.Server;
@@ -110,6 +108,7 @@ public class Startup : AppStartup
// setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常 // setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常
} }
; ;
services.AddMvcFilter<RequestAuditFilter>();
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings)) .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings))
@@ -138,7 +137,9 @@ public class Startup : AppStartup
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return true; if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested && logMsg.LogLevel >= LogLevel.Warning) return false;
if (string.IsNullOrEmpty(logMsg.Message)) return false;
else return true;
}; };
options.MessageFormat = (logMsg) => options.MessageFormat = (logMsg) =>
@@ -188,39 +189,40 @@ public class Startup : AppStartup
#region api日志 #region api日志
//Monitor日志配置 //Monitor日志配置
services.AddMonitorLogging(options => //services.AddMonitorLogging(options =>
{ //{
options.JsonIndented = true;// 是否美化 JSON // options.JsonIndented = true;// 是否美化 JSON
options.GlobalEnabled = false;//全局启用 // options.GlobalEnabled = false;//全局启用
options.ConfigureLogger((logger, logContext, context) => // options.ConfigureLogger((logger, logContext, context) =>
{ // {
var httpContext = context.HttpContext;//获取httpContext // var httpContext = context.HttpContext;//获取httpContext
//获取客户端信息 // //获取客户端信息
var client = App.GetService<IAppService>().UserAgent; // var client = App.GetService<IAppService>().UserAgent;
// 获取控制器/操作描述器 // // 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; // var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性 // //操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}"; // var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name]; // var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name];
//获取特性 // //获取特性
option = desc.Value;//则将操作名称赋值为控制器上写的title // option = desc.Value;//则将操作名称赋值为控制器上写的title
// logContext.Set(LoggingConst.CateGory, option);//传操作名称
// logContext.Set(LoggingConst.Operation, option);//传操作名称
// logContext.Set(LoggingConst.Client, client);//客户端信息
// logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址
// logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法
// });
//});
logContext.Set(LoggingConst.CateGory, option);//传操作名称
logContext.Set(LoggingConst.Operation, option);//传操作名称
logContext.Set(LoggingConst.Client, client);//客户端信息
logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址
logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法
});
});
//日志写入数据库配置 //日志写入数据库配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options => services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return logMsg.LogName == "System.Logging.LoggingMonitor";//只写入LoggingMonitor日志 return logMsg.LogName == "System.Logging.RequestAudit";
}; };
}); });

View File

@@ -19,8 +19,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.Extension; using ThingsGateway.Extension;

View File

@@ -14,7 +14,6 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -29,7 +28,6 @@ using System.Text.Unicode;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.Extension; using ThingsGateway.Extension;
using ThingsGateway.Logging;
using ThingsGateway.NewLife.Caching; using ThingsGateway.NewLife.Caching;
namespace ThingsGateway.Server; namespace ThingsGateway.Server;
@@ -89,6 +87,8 @@ public class Startup : AppStartup
} }
; ;
services.AddMvcFilter<RequestAuditFilter>();
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings)) .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings))
//.AddXmlSerializerFormatters() //.AddXmlSerializerFormatters()
@@ -161,7 +161,9 @@ public class Startup : AppStartup
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return true; if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested && logMsg.LogLevel >= LogLevel.Warning) return false;
if (string.IsNullOrEmpty(logMsg.Message)) return false;
else return true;
}; };
options.MessageFormat = (logMsg) => options.MessageFormat = (logMsg) =>
@@ -211,39 +213,39 @@ public class Startup : AppStartup
#region api日志 #region api日志
//Monitor日志配置 //Monitor日志配置
services.AddMonitorLogging(options => //services.AddMonitorLogging(options =>
{ //{
options.JsonIndented = true;// 是否美化 JSON // options.JsonIndented = true;// 是否美化 JSON
options.GlobalEnabled = false;//全局启用 // options.GlobalEnabled = false;//全局启用
options.ConfigureLogger((logger, logContext, context) => // options.ConfigureLogger((logger, logContext, context) =>
{ // {
var httpContext = context.HttpContext;//获取httpContext // var httpContext = context.HttpContext;//获取httpContext
//获取客户端信息 // //获取客户端信息
var userAgent = App.GetService<IAppService>().UserAgent; // var userAgent = App.GetService<IAppService>().UserAgent;
// 获取控制器/操作描述器 // // 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; // var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性 // //操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}"; // var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name]; // var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name];
//获取特性 // //获取特性
option = desc.Value;//则将操作名称赋值为控制器上写的title // option = desc.Value;//则将操作名称赋值为控制器上写的title
logContext.Set(LoggingConst.CateGory, option);//传操作名称 // logContext.Set(LoggingConst.CateGory, option);//传操作名称
logContext.Set(LoggingConst.Operation, option);//传操作名称 // logContext.Set(LoggingConst.Operation, option);//传操作名称
logContext.Set(LoggingConst.Client, userAgent);//客户端信息 // logContext.Set(LoggingConst.Client, userAgent);//客户端信息
logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址 // logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址
logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法 // logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法
}); // });
}); //});
//日志写入数据库配置 //日志写入数据库配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options => services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return logMsg.LogName == "System.Logging.LoggingMonitor";//只写入LoggingMonitor日志 return logMsg.LogName == "System.Logging.RequestAudit";
}; };
}); });

View File

@@ -51,6 +51,9 @@
<!--动态适用GC--> <!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<!--<TieredCompilation>false</TieredCompilation>-->
<!--使用自托管线程池--> <!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> <!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->

View File

@@ -16,7 +16,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.4" /> <PackageReference Include="TouchSocket.Dmtp" Version="3.1.5" />
</ItemGroup> </ItemGroup>

View File

@@ -21,7 +21,7 @@ namespace ThingsGateway.Upgrade;
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)] [ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
[Route("openApi/autoUpdate")] [Route("openApi/autoUpdate")]
[RolePermission] [RolePermission]
[LoggingMonitor] [RequestAudit]
[Authorize(AuthenticationSchemes = "Bearer")] [Authorize(AuthenticationSchemes = "Bearer")]
public class AutoUpdateController : ControllerBase public class AutoUpdateController : ControllerBase
{ {

View File

@@ -18,8 +18,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.Extension; using ThingsGateway.Extension;

View File

@@ -14,7 +14,6 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -29,7 +28,6 @@ using System.Text.Unicode;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor; using ThingsGateway.Admin.Razor;
using ThingsGateway.Extension; using ThingsGateway.Extension;
using ThingsGateway.Logging;
namespace ThingsGateway.UpgradeServer; namespace ThingsGateway.UpgradeServer;
@@ -85,6 +83,7 @@ public class Startup : AppStartup
// setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常 // setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常
} }
; ;
services.AddMvcFilter<RequestAuditFilter>();
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings)) .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings))
@@ -158,16 +157,9 @@ public class Startup : AppStartup
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return true; if (App.HostApplicationLifetime.ApplicationStopping.IsCancellationRequested && logMsg.LogLevel >= LogLevel.Warning) return false;
////如果不是LoggingMonitor日志才格式化 if (string.IsNullOrEmpty(logMsg.Message)) return false;
//if (logMsg.LogName != "System.Logging.LoggingMonitor") else return true;
//{
// return true;
//}
//else
//{
// return false;
//}
}; };
options.MessageFormat = (logMsg) => options.MessageFormat = (logMsg) =>
@@ -217,39 +209,39 @@ public class Startup : AppStartup
#region api日志 #region api日志
//Monitor日志配置 //Monitor日志配置
services.AddMonitorLogging(options => //services.AddMonitorLogging(options =>
{ //{
options.JsonIndented = true;// 是否美化 JSON // options.JsonIndented = true;// 是否美化 JSON
options.GlobalEnabled = false;//全局启用 // options.GlobalEnabled = false;//全局启用
options.ConfigureLogger((logger, logContext, context) => // options.ConfigureLogger((logger, logContext, context) =>
{ // {
var httpContext = context.HttpContext;//获取httpContext // var httpContext = context.HttpContext;//获取httpContext
//获取客户端信息 // //获取客户端信息
var client = App.GetService<IAppService>().UserAgent; // var client = App.GetService<IAppService>().UserAgent;
// 获取控制器/操作描述器 // // 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; // var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性 // //操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}"; // var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name]; // var desc = App.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name];
//获取特性 // //获取特性
option = desc.Value;//则将操作名称赋值为控制器上写的title // option = desc.Value;//则将操作名称赋值为控制器上写的title
logContext.Set(LoggingConst.CateGory, option);//传操作名称 // logContext.Set(LoggingConst.CateGory, option);//传操作名称
logContext.Set(LoggingConst.Operation, option);//传操作名称 // logContext.Set(LoggingConst.Operation, option);//传操作名称
logContext.Set(LoggingConst.Client, client);//客户端信息 // logContext.Set(LoggingConst.Client, client);//客户端信息
logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址 // logContext.Set(LoggingConst.Path, httpContext.Request.Path.Value);//请求地址
logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法 // logContext.Set(LoggingConst.Method, httpContext.Request.Method);//请求方法
}); // });
}); //});
//日志写入数据库配置 //日志写入数据库配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options => services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{ {
options.WriteFilter = (logMsg) => options.WriteFilter = (logMsg) =>
{ {
return logMsg.LogName == "System.Logging.LoggingMonitor";//只写入LoggingMonitor日志 return logMsg.LogName == "System.Logging.RequestAudit";
}; };
}); });

View File

@@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>10.6.16</Version> <Version>10.6.31</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>