release:6.0.4.38

refactor: 支持blazor hybrid;
feat(s7): 支持多写;
This commit is contained in:
Diego
2024-07-30 03:57:43 +08:00
parent ad3cc3ebb6
commit e49507cd14
201 changed files with 3041 additions and 868 deletions

View File

@@ -25,6 +25,8 @@ using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using TouchSocket.Core;
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
namespace ThingsGateway.Admin.Application;
@@ -403,7 +405,7 @@ public class JWTEncryption
var tokenHandler = new JsonWebTokenHandler();
try
{
var tokenValidationResult = tokenHandler.ValidateToken(accessToken, tokenValidationParameters);
var tokenValidationResult = tokenHandler.ValidateTokenAsync(accessToken, tokenValidationParameters).GetFalseAwaitResult();
if (!tokenValidationResult.IsValid) return (false, null, tokenValidationResult);
var jsonWebToken = tokenValidationResult.SecurityToken as JsonWebToken;

View File

@@ -122,7 +122,25 @@ public static class StartupExtensions
return parameterInstances;
}
/// <summary>
/// 解析方法参数实例
/// </summary>
private static object[] ResolveMethodParameterInstances(IServiceProvider service, MethodInfo method)
{
// 获取方法所有参数
var parameters = method.GetParameters();
var parameterInstances = new object[parameters.Length];
parameterInstances[0] = service;
// 解析服务
for (var i = 1; i < parameters.Length; i++)
{
var parameter = parameters[i];
parameterInstances[i] = service.GetRequiredService(parameter.ParameterType);
}
return parameterInstances;
}
/// <summary>
/// 对配置文件名进行分组
/// </summary>
@@ -173,6 +191,26 @@ public static class StartupExtensions
method.Invoke(startup, ResolveMethodParameterInstances(app, method));
}
}
// 遍历所有
foreach (var startup in startups)
{
var type = startup.GetType();
// 获取所有符合依赖注入格式的方法,如返回值 void且第一个参数是 IApplicationBuilder 类型
var configureMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(u => u.ReturnType == typeof(void)
&& u.GetParameters().Length > 0
&& u.GetParameters().First().ParameterType == typeof(IServiceProvider));
if (!configureMethods.Any()) continue;
// 自动安装属性调用
foreach (var method in configureMethods)
{
method.Invoke(startup, ResolveMethodParameterInstances(App.RootServices, method));
}
}
AppStartups.Clear();
}

View File

@@ -13,6 +13,8 @@ using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using ThingsGateway.Core;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Admin.Application;

View File

@@ -11,6 +11,9 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.Admin.Application;
using ThingsGateway.Core;
namespace ThingsGateway.Gateway.Application;
/// <summary>

View File

@@ -11,6 +11,10 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.Admin.Application;
using ThingsGateway.Core;
using ThingsGateway.Foundation;
namespace ThingsGateway.Gateway.Application;
/// <summary>

View File

@@ -12,6 +12,8 @@ using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Mvc;
using ThingsGateway.Admin.Application;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -26,6 +28,8 @@ public class GatewayExportController : ControllerBase
private readonly IDeviceService _deviceService;
private readonly IRpcLogService _rpcLogService;
private readonly IVariableService _variableService;
private readonly IImportExportService _importExportService;
/// <summary>
/// <inheritdoc cref="GatewayExportController"/>
@@ -35,7 +39,8 @@ public class GatewayExportController : ControllerBase
IDeviceService deviceService,
IVariableService variableService,
IBackendLogService backendLogService,
IRpcLogService rpcLogService
IRpcLogService rpcLogService,
IImportExportService importExportService
)
{
_backendLogService = backendLogService;
@@ -43,6 +48,8 @@ public class GatewayExportController : ControllerBase
_channelService = channelService;
_deviceService = deviceService;
_variableService = variableService;
_importExportService = importExportService;
}
/// <summary>
@@ -54,8 +61,9 @@ public class GatewayExportController : ControllerBase
{
input.IsPage = false;
input.IsVirtualScroll = false;
var fileStreamResult = await _deviceService.ExportDeviceAsync(input, PluginTypeEnum.Business);
return fileStreamResult;
var sheets = await _deviceService.ExportDeviceAsync(input, PluginTypeEnum.Business);
return await _importExportService.ExportAsync<Device>(sheets, "BusinessDevice", false);
}
/// <summary>
@@ -68,8 +76,8 @@ public class GatewayExportController : ControllerBase
input.IsPage = false;
input.IsVirtualScroll = false;
var fileStreamResult = await _channelService.ExportChannelAsync(input);
return fileStreamResult;
var sheets = await _channelService.ExportChannelAsync(input);
return await _importExportService.ExportAsync<Channel>(sheets, "Channel", false);
}
/// <summary>
@@ -82,8 +90,8 @@ public class GatewayExportController : ControllerBase
input.IsPage = false;
input.IsVirtualScroll = false;
var fileStreamResult = await _deviceService.ExportDeviceAsync(input, PluginTypeEnum.Collect);
return fileStreamResult;
var sheets = await _deviceService.ExportDeviceAsync(input, PluginTypeEnum.Collect);
return await _importExportService.ExportAsync<Device>(sheets, "CollectDevice", false);
}
/// <summary>
@@ -96,7 +104,7 @@ public class GatewayExportController : ControllerBase
input.IsPage = false;
input.IsVirtualScroll = false;
var fileStreamResult = await _variableService.ExportVariableAsync(input);
return fileStreamResult;
var sheets = await _variableService.ExportVariableAsync(input);
return await _importExportService.ExportAsync<Variable>(sheets, "Variable", false);
}
}

View File

@@ -17,6 +17,9 @@ using NewLife.Extension;
using SqlSugar;
using ThingsGateway.Admin.Application;
using ThingsGateway.Core;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -35,7 +38,7 @@ public class RuntimeInfoControler : ControllerBase
[HttpGet("deviceList")]
public SqlSugarPagedList<DeviceData> GetCollectDeviceList([FromQuery] DevicePageInput input)
{
var data = GlobalData.CollectDevices.Values
var data = GlobalData.ReadOnlyCollectDevices.Select(a => a.Value)
.WhereIF(!string.IsNullOrEmpty(input.Name), u => u.Name.Contains(input.Name))
.WhereIF(input.ChannelId != null, u => u.ChannelId == input.ChannelId)
.WhereIF(!string.IsNullOrEmpty(input.PluginName), u => u.PluginName == input.PluginName)

View File

@@ -10,12 +10,13 @@
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
namespace ThingsGateway.Server;
/// <inheritdoc/>
internal static class CacheExtensions
public static class CacheExtensions
{
private static int? _age;

View File

@@ -0,0 +1,64 @@
{
"ThingsGateway.Gateway.Application.ConfigInfoControler": {
"ConfigInfoControler": "Get configuration information",
"GetChannelList": "Get channel information",
"GetCollectDeviceList": "Get device information",
"GetVariableList": "Get variable information"
},
"ThingsGateway.Gateway.Application.ControlControler": {
"ControlControler": "Device control",
"PasueCollectThread": "Control collection thread start/stop",
"PasueBusinessThread": "Control business thread start/stop",
"RestartCollectDeviceThread": "Restart collection thread",
"RestartBusinessDeviceThread": "Restart business thread",
"WriteDeviceMethods": "Write variables"
},
"ThingsGateway.Gateway.Application.RuntimeInfoControler": {
"RuntimeInfoControler": "Get runtime information",
"GetCollectDeviceList": "Get device information",
"GetVariableList": "Get variable information",
"GetRealAlarmList": "Get real-time alarm information"
},
"ThingsGateway.FileService": {
"FileNullError": "File cannot be empty",
"FileLengthError": "File size cannot exceed {0} M",
"FileTypeError": "Not supported format {0}"
},
//controller
"ThingsGateway.Admin.Application.AuthController": {
//auth
"AuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
"ThingsGateway.Admin.Application.TestController": {
//auth
"TestController": "Test API",
"Test": "Test"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth
"OpenApiAuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
"ThingsGateway.UnifyResultProvider": {
"TokenOver": "Login has expired, please login again",
"NoPermission": "Access denied, no permission"
},
"ThingsGateway.Admin.Application.AuthService": {
"SingleLoginWarn": "Your account is logged in elsewhere",
"UserNull": "User {0} does not exist",
"MustDesc": "Password needs to be encrypted with DESC before passing",
"PasswordError": "Too many password errors, please try again in {0} minutes",
"UserDisable": "Account {0} has been disabled",
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
"AuthErrorMax": "Account password error, will be locked for {1} minutes after exceeding {0} times, error count {2}"
}
}

View File

@@ -0,0 +1,64 @@
{
"ThingsGateway.Gateway.Application.ConfigInfoControler": {
"ConfigInfoControler": "获取配置信息",
"GetChannelList": "获取通道信息",
"GetCollectDeviceList": "获取设备信息",
"GetVariableList": "获取变量信息"
},
"ThingsGateway.Gateway.Application.ControlControler": {
"ControlControler": "设备控制",
"PasueCollectThread": "控制采集线程启停",
"PasueBusinessThread": "控制业务线程启停",
"RestartCollectDeviceThread": "重启采集线程",
"RestartBusinessDeviceThread": "重启业务线程",
"WriteDeviceMethods": "写入变量"
},
"ThingsGateway.Gateway.Application.RuntimeInfoControler": {
"RuntimeInfoControler": "获取运行态信息",
"GetCollectDeviceList": "获取设备信息",
"GetVariableList": "获取变量信息",
"GetRealAlarmList": "获取实时报警信息"
},
"ThingsGateway.FileService": {
"FileNullError": "文件不能为空",
"FileLengthError": "文件大小不允许超过 {0} M",
"FileTypeError": "不支持 {0} 格式"
},
//controller
"ThingsGateway.Admin.Application.AuthController": {
//auth
"AuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
"ThingsGateway.Admin.Application.TestController": {
//auth
"TestController": "测试API",
"Test": "测试"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth
"OpenApiAuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
"ThingsGateway.UnifyResultProvider": {
"TokenOver": "登录已过期,请重新登录",
"NoPermission": "禁止访问,没有权限"
},
"ThingsGateway.Admin.Application.AuthService": {
"SingleLoginWarn": "您的账号已在别处登录",
"UserNull": "用户 {0} 不存在",
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
"UserDisable": "账号 {0} 已停用",
"MustDesc": "密码需要DESC加密后传入"
}
}

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using System.Text.Json;
namespace Microsoft.AspNetCore.Builder;

View File

@@ -9,6 +9,7 @@
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace ThingsGateway.Server;

View File

@@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;

View File

@@ -0,0 +1,102 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
// ------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Mapster;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using NewLife;
using System.Globalization;
using System.Reflection;
using ThingsGateway;
using ThingsGateway.Admin.Application;
using ThingsGateway.Core.Extension;
public class ApiPermissionService : IApiPermissionService
{
/// <inheritdoc />
public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector()
{
var cacheKey = $"{nameof(ApiPermissionTreeSelector)}-{CultureInfo.CurrentUICulture.Name}";
var permissions = NetCoreApp.CacheService.GetOrCreate(cacheKey, entry =>
{
List<OpenApiPermissionTreeSelector> permissions = new();//权限列表
// 获取所有需要数据权限的控制器
var controllerTypes =
NetCoreApp.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(RolePermissionAttribute), false));
foreach (var controller in controllerTypes)
{
//获取数据权限特性
var route = controller.GetCustomAttributes<Microsoft.AspNetCore.Mvc.RouteAttribute>().FirstOrDefault();
if (route == null) continue;
var description = controller.GetTypeDisplayName();
var routeName = ResourceUtil.GetRouteName(controller.Name, route.Template);//赋值路由名称
OpenApiPermissionTreeSelector openApiGroup = new() { ApiName = description ?? routeName, ApiRoute = routeName };
//获取所有方法
var menthods = controller.GetRuntimeMethods();
//遍历方法
foreach (var menthod in menthods)
{
//获取忽略数据权限特性
var ignoreRolePermission = menthod.GetCustomAttribute<IgnoreRolePermissionAttribute>();
if (ignoreRolePermission == null)//如果是空的代表需要数据权限
{
//获取接口描述
var methodDesc = controller.GetMethodDisplayName(menthod.Name);
//if (methodDesc != null)
{
//默认路由名称
var apiRoute = menthod.Name.ToLowerCamelCase();
//获取get特性
var requestGet = menthod.GetCustomAttribute<Microsoft.AspNetCore.Mvc.HttpGetAttribute>();
if (requestGet != null)//如果是get方法
apiRoute = requestGet.Template;
else
{
//获取post特性
var requestPost = menthod.GetCustomAttribute<Microsoft.AspNetCore.Mvc.HttpPostAttribute>();
if (requestPost != null)//如果是post方法
apiRoute = requestPost.Template;
else
continue;
}
//apiRoute = route.Template + $"/{apiRoute}";
apiRoute = routeName + $"/{apiRoute}";
//添加到权限列表
openApiGroup.Children ??= new();
openApiGroup.Children.Add(new OpenApiPermissionTreeSelector
{
ApiName = methodDesc,
ApiRoute = apiRoute,
});
}
}
}
permissions.Add(openApiGroup);
}
return permissions;
});
return permissions;
}
}

View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.WebUtilities;
using System.Net;
using System.Security.Claims;
using UAParser;
namespace ThingsGateway.Admin.Application;
public class AspNetCoreAppService : IAppService
{
public string GetReturnUrl(string returnUrl)
{
var url = QueryHelpers.AddQueryString(CookieAuthenticationDefaults.LoginPath, new Dictionary<string, string?>
{
["ReturnUrl"] = returnUrl
});
return url;
}
public ClientInfo? ClientInfo
{
get
{
var str = App.HttpContext?.Request?.Headers?.UserAgent;
ClientInfo? clientInfo = null;
if (str.HasValue)
{
clientInfo = Parser.GetDefault().Parse(str);
}
return clientInfo;
}
}
public ClaimsPrincipal? User => App.User;
public IPAddress? RemoteIpAddress => App.HttpContext?.Connection?.RemoteIpAddress;
public int LocalPort => App.HttpContext.Connection.LocalPort;
}

View File

@@ -0,0 +1,70 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.Core.Json.Extension;
namespace ThingsGateway.Admin.Application;
public class AuthRazorService : IAuthRazorService
{
private AjaxService AjaxService { get;set; }
public AuthRazorService(AjaxService ajaxService)
{
AjaxService = ajaxService;
}
/// <summary>
/// 用户登录
/// </summary>
public async Task<UnifyResult<LoginOutput>> LoginAsync(LoginInput input)
{
var ajaxOption = new AjaxOption
{
Url = "/api/auth/login",
Method = "POST",
Data = input,
};
var str = await AjaxService.InvokeAsync(ajaxOption);
if (str != null)
{
var ret = str.RootElement.GetRawText().FromSystemTextJsonString<UnifyResult<LoginOutput>>();
return ret;
}
else
{
throw new ArgumentNullException();
}
}
/// <summary>
/// 注销当前用户
/// </summary>
public async Task<UnifyResult<object>> LoginOutAsync()
{
var ajaxOption = new AjaxOption
{
Url = "/api/auth/logout",
Method = "POST",
};
using var str = await AjaxService.InvokeAsync(ajaxOption);
if (str != null)
{
var ret = str.RootElement.GetRawText().FromSystemTextJsonString<UnifyResult<object>>();
return ret;
}
else
{
throw new ArgumentNullException();
}
}
}

View File

@@ -17,6 +17,8 @@ using SqlSugar;
using System.Security.Claims;
using ThingsGateway.Core;
using Yitter.IdGenerator;
namespace ThingsGateway.Admin.Application;

View File

@@ -70,7 +70,7 @@ public class FileService : IFileService
/// <param name="pPath">保存路径</param>
/// <param name="file">文件</param>
/// <returns>最终全路径</returns>
public async Task<string> UploadFileAsync(string pPath, IBrowserFile file)
public async Task<string> UploadFileAsync( IBrowserFile file,string pPath= "imports")
{
return await file.StorageLocal(pPath);
}

View File

@@ -38,7 +38,7 @@ public interface IFileService
/// <param name="pPath">保存路径</param>
/// <param name="file">文件流</param>
/// <returns>最终全路径</returns>
Task<string> UploadFileAsync(string pPath, IBrowserFile file);
Task<string> UploadFileAsync( IBrowserFile file, string pPath="imports");
/// <summary>
/// 验证文件信息

View File

@@ -19,6 +19,7 @@ using System.Reflection;
using System.Text;
using System.Web;
using ThingsGateway.Admin.Application;
using ThingsGateway.Core;
using Yitter.IdGenerator;
@@ -47,26 +48,7 @@ public class ImportExportService : IImportExportService
public async Task<FileStreamResult> ExportAsync<T>(object input, string fileName, bool isDynamicExcelColumn = true) where T : class
{
if (!fileName.Contains("."))
fileName += ".xlsx";
var path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "exports");
Directory.CreateDirectory(path);
string searchPattern = $"*{fileName}"; // 文件名匹配模式
string[] files = Directory.GetFiles(path, searchPattern);
//删除同后缀的文件
var whereFiles = files.Where(file => File.GetLastWriteTime(file) < DateTime.Now.AddMinutes(-2));
foreach (var file in whereFiles)
{
if (File.Exists(file))
{
File.SetAttributes(file, FileAttributes.Normal);
File.Delete(file);
}
}
var path = ImportExportUtil.GetFileDir(ref fileName);
fileName = YitIdHelper.NextId() + fileName;
var filePath = Path.Combine(path, fileName);
@@ -92,7 +74,7 @@ public class ImportExportService : IImportExportService
public async Task<string> UploadFileAsync(IBrowserFile file)
{
_fileService.Verification(file);
return await _fileService.UploadFileAsync("imports", file);
return await _fileService.UploadFileAsync(file);
}
#endregion

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor;
using ThingsGateway.Logging;
using UAParser;
namespace ThingsGateway.Server;
[AppStartup(-99999)]
public class Startup : AppStartup
{
public void ConfigBlazorServer(IServiceCollection services)
{
#region api日志
//Monitor日志配置
services.AddMonitorLogging(options =>
{
options.JsonIndented = true;// 是否美化 JSON
options.GlobalEnabled = false;//全局启用
options.ConfigureLogger((logger, logContext, context) =>
{
var httpContext = context.HttpContext;//获取httpContext
//获取头
var userAgent = httpContext.Request.Headers["User-Agent"];
if (string.IsNullOrEmpty(userAgent)) userAgent = "Other";//如果没有这个头就指定一个
var parser = Parser.GetDefault();
//获取客户端信息
var client = parser.Parse(userAgent);
// 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性
var option = $"{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
var desc = NetCoreApp.CreateLocalizerByType(controllerActionDescriptor.ControllerTypeInfo.AsType())[controllerActionDescriptor.MethodInfo.Name];
//获取特性
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);//请求方法
});
});
//日志写入数据库配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName == "System.Logging.LoggingMonitor";//只写入LoggingMonitor日志
};
});
#endregion api日志
services.AddSingleton<IUnifyResultProvider, UnifyResultProvider>();
services.AddSingleton<IAuthService, AuthService>();
services.AddScoped<IAuthRazorService, AuthRazorService>();
services.AddSingleton<IAppService, AspNetCoreAppService>();
services.AddSingleton<IApiPermissionService, ApiPermissionService>();
services.AddSingleton<IFileService, FileService>();
services.AddSingleton<IImportExportService, ImportExportService>();
services.AddSingleton<ISignalrNoticeService, SignalrNoticeService>();
services.AddSingleton<IAuthService, AuthService>();
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="$(SolutionDir)Version.props" />
<Import Project="$(SolutionDir)PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<ProjectReference Include="$(SolutionDir)\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
</ItemGroup>
<PropertyGroup Condition="'$(SolutionName)'=='ThingsGateway - Admin'">
<DefineConstants>Admin</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(SolutionName)' != 'ThingsGateway - Admin'">
<ProjectReference Include="$(SolutionDir)\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,17 @@

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.Extensions.Localization
@using System.ComponentModel
@using System.ComponentModel.DataAnnotations
@using ThingsGateway.Razor;

View File

@@ -8,23 +8,4 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.SignalR;
namespace ThingsGateway.Admin.Application;
public class UserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var feature = connection.Features.Get<IHttpContextFeature>();
var UserId = feature!.HttpContext!.Request.Headers[ClaimConst.UserId].FirstOrDefault()?.ToLong();
if (UserId > 0)
{
return $"{UserId}";
}
return connection.ConnectionId;
}
}
global using Microsoft.Extensions.Options;

View File

@@ -32,11 +32,13 @@ public class OperDescAttribute : MoAttribute
/// 日志消息队列(线程安全)
/// </summary>
private static readonly ConcurrentQueue<SysOperateLog> _logMessageQueue = new();
private static readonly IAppService AppService;
static OperDescAttribute()
{
// 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
AppService = NetCoreApp.RootServices.GetService<IAppService>();
}
public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null)
@@ -89,8 +91,8 @@ public class OperDescAttribute : MoAttribute
private static async Task ProcessQueue()
{
var db = DbContext.Db.GetConnectionScopeWithAttr<SysOperateLog>().CopyNew();
var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!(appLifetime.ApplicationStopping.IsCancellationRequested || appLifetime.ApplicationStopped.IsCancellationRequested))
var appLifetime = NetCoreApp.RootServices!.GetService<IHostApplicationLifetime>()!;
while (!((appLifetime?.ApplicationStopping ?? default).IsCancellationRequested || (appLifetime?.ApplicationStopped ?? default).IsCancellationRequested))
{
try
{
@@ -98,7 +100,7 @@ public class OperDescAttribute : MoAttribute
{
await db.InsertableWithAttr(_logMessageQueue.ToListWithDequeue()).ExecuteCommandAsync();//入库
}
await Task.Delay(3000, appLifetime.ApplicationStopping).ConfigureAwait(false);
await Task.Delay(3000, appLifetime?.ApplicationStopping ?? default).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -109,13 +111,8 @@ public class OperDescAttribute : MoAttribute
private SysOperateLog GetOperLog(Type? localizerType, MethodContext context)
{
var str = App.HttpContext?.Request?.Headers?.UserAgent;
var methodBase = context.Method;
ClientInfo? clientInfo = null;
if (str.HasValue)
{
clientInfo = Parser.GetDefault().Parse(str);
}
ClientInfo? clientInfo = AppService.ClientInfo;
string? paramJson = null;
if (IsRecordPar)
{
@@ -134,10 +131,10 @@ public class OperDescAttribute : MoAttribute
//操作日志表实体
var log = new SysOperateLog
{
Name = (localizerType == null ? App.CreateLocalizerByType(typeof(OperDescAttribute)) : App.CreateLocalizerByType(localizerType))![Description],
Name = (localizerType == null ? NetCoreApp.CreateLocalizerByType(typeof(OperDescAttribute)) : NetCoreApp.CreateLocalizerByType(localizerType))![Description],
Category = LogCateGoryEnum.Operate,
ExeStatus = true,
OpIp = App.HttpContext?.Connection?.RemoteIpAddress?.MapToIPv4()?.ToString(),
OpIp = AppService?.RemoteIpAddress?.MapToIPv4()?.ToString(),
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
OpTime = DateTime.Now,

View File

@@ -27,8 +27,8 @@ internal class AdminTaskService : BackgroundService
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//实现 删除过期日志 功能,不需要精确的时间
var daysAgo = App.Configuration.GetSection("LogJob:DaysAgo").Get<int?>() ?? 1;
var verificatInfoService = App.RootServices.GetService<IVerificatInfoService>();
var daysAgo = NetCoreApp.Configuration.GetSection("LogJob:DaysAgo").Get<int?>() ?? 1;
var verificatInfoService = NetCoreApp.RootServices.GetService<IVerificatInfoService>();
while (!stoppingToken.IsCancellationRequested)
{

View File

@@ -82,7 +82,7 @@ public class HardwareInfoService : BackgroundService
string currentPath = Directory.GetCurrentDirectory();
DriveInfo drive = new(Path.GetPathRoot(currentPath));
HardwareInfoConfig = App.Configuration.GetSection(nameof(HardwareInfoConfig)).Get<HardwareInfoConfig?>() ?? new HardwareInfoConfig();
HardwareInfoConfig = NetCoreApp.Configuration.GetSection(nameof(HardwareInfoConfig)).Get<HardwareInfoConfig?>() ?? new HardwareInfoConfig();
HardwareInfoConfig.DaysAgo = Math.Min(Math.Max(HardwareInfoConfig.DaysAgo, 1), 7);
HardwareInfoConfig.RealInterval = 30;
HardwareInfoConfig.Enable = true;
@@ -90,7 +90,7 @@ public class HardwareInfoService : BackgroundService
APPInfo.DriveInfo = drive;
APPInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
APPInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
APPInfo.Environment = App.IsDevelopment ? "Development" : "Production";
APPInfo.Environment = NetCoreApp.IsDevelopment ? "Development" : "Production";
APPInfo.UUID = DESCEncryption.Encrypt(APPInfo.MachineInfo.UUID + APPInfo.MachineInfo.Guid + APPInfo.MachineInfo.DiskID);
APPInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
@@ -136,7 +136,7 @@ public class HardwareInfoService : BackgroundService
catch (Exception ex)
{
if (!error)
_logger.LogWarning(ex, App.CreateLocalizerByType(this.GetType())["GetHardwareInfoFail"]);
_logger.LogWarning(ex, NetCoreApp.CreateLocalizerByType(this.GetType())["GetHardwareInfoFail"]);
error = true;
}
}

View File

@@ -1,9 +1,5 @@
{
"ThingsGateway.FileService": {
"FileNullError": "File cannot be empty",
"FileLengthError": "File size cannot exceed {0} M",
"FileTypeError": "Not supported format {0}"
},
"ThingsGateway.Admin.Application.APPInfo": {
"Environment": "HostEnvironment",
"FrameworkDescription": ".NETFramework",
@@ -19,24 +15,6 @@
"Battery": "Battery"
},
//controller
"ThingsGateway.Admin.Application.AuthController": {
//auth
"AuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
"ThingsGateway.Admin.Application.TestController": {
//auth
"TestController": "Test API",
"Test": "Test"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth
"OpenApiAuthController": "Login API",
"LoginAsync": "Login",
"LogoutAsync": "Logout"
},
//oper
"ThingsGateway.Admin.Application.OperDescAttribute": {
@@ -83,10 +61,6 @@
"ExitSession": "Force session off"
},
"ThingsGateway.Admin.Application.UnifyResultProvider": {
"TokenOver": "Login has expired, please login again",
"NoPermission": "Access denied, no permission"
},
//service
"ThingsGateway.Admin.Application.SessionService": {
@@ -96,15 +70,6 @@
"ThingsGateway.Admin.Application.HardwareInfoService": {
"GetHardwareInfoFail": "Get Hardwareinfo Fail"
},
"ThingsGateway.Admin.Application.AuthService": {
"SingleLoginWarn": "Your account is logged in elsewhere",
"UserNull": "User {0} does not exist",
"MustDesc": "Password needs to be encrypted with DESC before passing",
"PasswordError": "Too many password errors, please try again in {0} minutes",
"UserDisable": "Account {0} has been disabled",
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
"AuthErrorMax": "Account password error, will be locked for {1} minutes after exceeding {0} times, error count {2}"
},
//dto
"ThingsGateway.Admin.Application.UserSelectorOutput": {

View File

@@ -1,10 +1,4 @@
{
"ThingsGateway.FileService": {
"FileNullError": "文件不能为空",
"FileLengthError": "文件大小不允许超过 {0} M",
"FileTypeError": "不支持 {0} 格式"
},
"ThingsGateway.Admin.Application.APPInfo": {
"Environment": "主机环境",
"FrameworkDescription": "NET框架",
@@ -20,24 +14,6 @@
"Battery": "电池"
},
//controller
"ThingsGateway.Admin.Application.AuthController": {
//auth
"AuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
"ThingsGateway.Admin.Application.TestController": {
//auth
"TestController": "测试API",
"Test": "测试"
},
"ThingsGateway.Admin.Application.OpenApiAuthController": {
//auth
"OpenApiAuthController": "登录API",
"LoginAsync": "登录",
"LogoutAsync": "注销"
},
//oper
"ThingsGateway.Admin.Application.OperDescAttribute": {
//dict
@@ -83,11 +59,6 @@
"ExitSession": "强退会话"
},
"ThingsGateway.Admin.Application.UnifyResultProvider": {
"TokenOver": "登录已过期,请重新登录",
"NoPermission": "禁止访问,没有权限"
},
//service
"ThingsGateway.Admin.Application.SessionService": {
},
@@ -96,14 +67,6 @@
"ThingsGateway.Admin.Application.HardwareInfoService": {
"GetHardwareInfoFail": "获取硬件信息出错"
},
"ThingsGateway.Admin.Application.AuthService": {
"SingleLoginWarn": "您的账号已在别处登录",
"UserNull": "用户 {0} 不存在",
"PasswordError": "密码错误次数过多,请 {0} 分钟后再试",
"AuthErrorMax": "账号密码错误,超过 {0} 次后将锁定 {1} 分钟,错误次数 {2} ",
"UserDisable": "账号 {0} 已停用",
"MustDesc": "密码需要DESC加密后传入"
},
//dto
"ThingsGateway.Admin.Application.UserSelectorOutput": {

View File

@@ -8,24 +8,24 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Net;
using System.Security.Claims;
using UAParser;
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 即时通讯集线器
/// </summary>
public interface ISysHub
{
/// <summary>
/// 退出登录
/// </summary>
/// <param name="message">消息</param>
/// <returns></returns>
Task LoginOut(string message);
/// <summary>
/// 新消息
/// </summary>
/// <param name="message">消息</param>
/// <returns></returns>
Task NewMessage(SignalRMessage message);
public interface IAppService
{
public ClientInfo? ClientInfo { get; }
public ClaimsPrincipal? User { get; }
public IPAddress? RemoteIpAddress { get; }
public string GetReturnUrl(string returnUrl);
}

View File

@@ -146,7 +146,7 @@ public class SysDictService : BaseService<SysDict>, ISysDictService
[OperDesc("EditWebsitePolicy")]
public async Task EditWebsitePolicyAsync(WebsitePolicy input)
{
var websiteOptions = App.Configuration?.GetSection(nameof(WebsiteOptions)).Get<WebsiteOptions>()!;
var websiteOptions = NetCoreApp.Configuration?.GetSection(nameof(WebsiteOptions)).Get<WebsiteOptions>()!;
if (websiteOptions.Demo)
{
throw Oops.Bah(Localizer["DemoCanotUpdateWebsitePolicy"]);
@@ -184,7 +184,7 @@ public class SysDictService : BaseService<SysDict>, ISysDictService
public async Task<AppConfig> GetAppConfigAsync()
{
var key = $"{CacheConst.Cache_SysDict}{DictTypeEnum.System}{nameof(AppConfig)}";//系统配置key
var appConfig = App.CacheService.Get<AppConfig>(key);
var appConfig = NetCoreApp.CacheService.Get<AppConfig>(key);
if (appConfig == null)
{
List<SysDict> sysDicts = await GetSystemConfigAsync();
@@ -211,7 +211,7 @@ public class SysDictService : BaseService<SysDict>, ISysDictService
//网站设置
appConfig.WebsitePolicy.WebStatus = sysDicts.FirstOrDefault(a => a.Category == nameof(WebsitePolicy) && a.Name == nameof(WebsitePolicy.WebStatus))?.Code.ToBoolean() ?? true;
appConfig.WebsitePolicy.CloseTip = sysDicts.FirstOrDefault(a => a.Category == nameof(WebsitePolicy) && a.Name == nameof(WebsitePolicy.CloseTip))?.Code ?? "";
App.CacheService.Set(key, appConfig);
NetCoreApp.CacheService.Set(key, appConfig);
}
return appConfig;
@@ -227,12 +227,12 @@ public class SysDictService : BaseService<SysDict>, ISysDictService
{
var key = CacheConst.Cache_SysDict + DictTypeEnum.Define;
var field = $"{category}:sysdict:{name}";
var sysDict = App.CacheService.HashGetOne<SysDict>(key, field);
var sysDict = NetCoreApp.CacheService.HashGetOne<SysDict>(key, field);
if (sysDict == null)
{
using var db = GetDB();
sysDict = await db.Queryable<SysDict>().FirstAsync(a => a.DictType == DictTypeEnum.System && a.Category == category && a.Name == a.Name);
App.CacheService.HashAdd(key, field, sysDict);
NetCoreApp.CacheService.HashAdd(key, field, sysDict);
}
return sysDict;
}
@@ -244,12 +244,12 @@ public class SysDictService : BaseService<SysDict>, ISysDictService
public async Task<List<SysDict>> GetSystemConfigAsync()
{
var key = $"{CacheConst.Cache_SysDict}{DictTypeEnum.System}";//系统配置key
var sysDicts = App.CacheService.Get<List<SysDict>>(key);
var sysDicts = NetCoreApp.CacheService.Get<List<SysDict>>(key);
if (sysDicts == null)
{
using var db = GetDB();
sysDicts = await db.Queryable<SysDict>().Where(a => a.DictType == DictTypeEnum.System).ToListAsync();
App.CacheService.Set(key, sysDicts);
NetCoreApp.CacheService.Set(key, sysDicts);
}
return sysDicts;
@@ -310,9 +310,9 @@ public class SysDictService : BaseService<SysDict>, ISysDictService
/// <returns></returns>
private void RefreshCache(DictTypeEnum define)
{
App.CacheService.Remove($"{CacheConst.Cache_SysDict}{define}");
NetCoreApp.CacheService.Remove($"{CacheConst.Cache_SysDict}{define}");
if (define == DictTypeEnum.System)
App.CacheService.Remove($"{CacheConst.Cache_SysDict}{define}{nameof(AppConfig)}");
NetCoreApp.CacheService.Remove($"{CacheConst.Cache_SysDict}{define}{nameof(AppConfig)}");
}
#endregion

View File

@@ -0,0 +1,19 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
// ------------------------------------------------------------------------------
using ThingsGateway.Admin.Application;
public interface IApiPermissionService
{
/// <inheritdoc />
public List<OpenApiPermissionTreeSelector> ApiPermissionTreeSelector();
}

View File

@@ -10,13 +10,16 @@
namespace ThingsGateway.Admin.Application;
/// <summary>
/// 通讯器常量
/// </summary>
public class HubConst
public interface IAuthRazorService
{
/// <summary>
/// 系统HubUrl
/// 用户登录
/// </summary>
public const string SysHubUrl = "/hubs/thingsgateway";
Task<UnifyResult<LoginOutput>> LoginAsync(LoginInput input);
/// <summary>
/// 注销当前用户
/// </summary>
Task<UnifyResult<object>> LoginOutAsync();
}

View File

@@ -10,7 +10,7 @@
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Mvc;
using System.Data;
namespace ThingsGateway.Admin.Application;
@@ -25,13 +25,6 @@ public interface ISysOperateLogService
/// <param name="category">日志分类</param>
Task DeleteAsync(LogCateGoryEnum category);
/// <summary>
/// 导出操作日志文件
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>文件流</returns>
Task<FileStreamResult> ExportFileAsync(QueryPageOptions input);
/// <summary>
/// 获取最新的十条日志
/// </summary>

View File

@@ -10,8 +10,6 @@
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Mvc;
using NewLife.Extension;
using SqlSugar;
@@ -22,12 +20,6 @@ namespace ThingsGateway.Admin.Application;
public class SysOperateLogService : BaseService<SysOperateLog>, ISysOperateLogService
{
private readonly IImportExportService _importExportService;
public SysOperateLogService(IImportExportService importExportService)
{
_importExportService = importExportService;
}
#region
@@ -100,40 +92,6 @@ public class SysOperateLogService : BaseService<SysOperateLog>, ISysOperateLogSe
#endregion
#region
/// <summary>
/// 导出日志
/// </summary>
/// <param name="input">IDataReader为空时导出全部</param>
/// <returns>文件流</returns>
[OperDesc("ExportOperLog", isRecordPar: false)]
public async Task<FileStreamResult> ExportFileAsync(IDataReader? input = null)
{
if (input != null)
{
return await _importExportService.ExportAsync<SysOperateLog>(input, "OperateLog");
}
using var db = GetDB();
var query = db.Queryable<SysOperateLog>().ExportIgnoreColumns();
var sqlObj = query.ToSql();
using IDataReader? dataReader = await db.Ado.GetDataReaderAsync(sqlObj.Key, sqlObj.Value);
return await _importExportService.ExportAsync<SysOperateLog>(dataReader, "OperateLog");
}
/// <summary>
/// 导出日志
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>文件流</returns>
public async Task<FileStreamResult> ExportFileAsync(QueryPageOptions input)
{
using var db = GetDB();
var query = db.GetQuery<SysOperateLog>(input).ExportIgnoreColumns();
var sqlObj = query.ToSql();
using IDataReader? dataReader = await db.Ado.GetDataReaderAsync(sqlObj.Key, sqlObj.Value);
return await ExportFileAsync(dataReader);
}
#endregion
}

View File

@@ -22,12 +22,12 @@ public class RelationService : BaseService<SysRelation>, IRelationService
public async Task<List<SysRelation>> GetRelationByCategoryAsync(RelationCategoryEnum category)
{
var key = $"{CacheConst.Cache_SysRelation}{category}";
var sysRelations = App.CacheService.Get<List<SysRelation>>(key);
var sysRelations = NetCoreApp.CacheService.Get<List<SysRelation>>(key);
if (sysRelations == null)
{
using var db = GetDB();
sysRelations = await db.Queryable<SysRelation>().Where(it => it.Category == category).ToListAsync();
App.CacheService.Set(key, sysRelations ?? new());//赋值空集合
NetCoreApp.CacheService.Set(key, sysRelations ?? new());//赋值空集合
}
return sysRelations;
@@ -196,7 +196,7 @@ public class RelationService : BaseService<SysRelation>, IRelationService
public void RefreshCache(RelationCategoryEnum category)
{
var key = $"{CacheConst.Cache_SysRelation}{category}";
App.CacheService.Remove(key);
NetCoreApp.CacheService.Remove(key);
}
#endregion

View File

@@ -100,12 +100,12 @@ public class SysResourceService : BaseService<SysResource>, ISysResourceService
/// <returns>全部资源列表</returns>
public async Task<List<SysResource>> GetAllAsync()
{
var sysResources = App.CacheService.Get<List<SysResource>>(CacheKey);
var sysResources = NetCoreApp.CacheService.Get<List<SysResource>>(CacheKey);
if (sysResources == null)
{
using var db = GetDB();
sysResources = await db.Queryable<SysResource>().ToListAsync();
App.CacheService.Set(CacheKey, sysResources);
NetCoreApp.CacheService.Set(CacheKey, sysResources);
}
return sysResources;
}
@@ -215,9 +215,9 @@ public class SysResourceService : BaseService<SysResource>, ISysResourceService
/// </summary>
public void RefreshCache()
{
App.CacheService.Remove(CacheKey);
NetCoreApp.CacheService.Remove(CacheKey);
//删除超级管理员的缓存
App.RootServices.GetRequiredService<ISysUserService>().DeleteUserFromCache(RoleConst.SuperAdminId);
NetCoreApp.RootServices.GetRequiredService<ISysUserService>().DeleteUserFromCache(RoleConst.SuperAdminId);
}
#endregion

View File

@@ -36,12 +36,12 @@ public class SysRoleService : BaseService<SysRole>, ISysRoleService
public async Task<List<SysRole>> GetAllAsync()
{
var key = CacheConst.Cache_SysRole;
var sysRoles = App.CacheService.Get<List<SysRole>>(key);
var sysRoles = NetCoreApp.CacheService.Get<List<SysRole>>(key);
if (sysRoles == null)
{
using var db = GetDB();
sysRoles = await db.Queryable<SysRole>().ToListAsync();
App.CacheService.Set(key, sysRoles);
NetCoreApp.CacheService.Set(key, sysRoles);
}
return sysRoles;
}
@@ -361,7 +361,7 @@ public class SysRoleService : BaseService<SysRole>, ISysRoleService
/// </summary>
public void RefreshCache()
{
App.CacheService.Remove(CacheConst.Cache_SysRole);//删除KEY
NetCoreApp.CacheService.Remove(CacheConst.Cache_SysRole);//删除KEY
}
#endregion

View File

@@ -0,0 +1,34 @@
using BootstrapBlazor.Components;
namespace ThingsGateway.Admin.Application;
public interface IEventService<TEntry>
{
Task Publish(string key, TEntry payload);
void Subscribe(string key, Func<TEntry, Task> callback);
void UnSubscribe(string key);
}
public class EventService<TEntry> : IEventService<TEntry>
{
private Dictionary<string, Func<TEntry, Task>> Cache { get; } = new(50);
public async Task Publish(string key, TEntry payload)
{
if (Cache.TryGetValue(key, out var func))
{
await func(payload);
}
}
public void Subscribe(string key, Func<TEntry, Task> callback)
{
Cache.TryAdd(key, callback);
}
public void UnSubscribe(string key)
{
Cache.Remove(key);
}
}

View File

@@ -0,0 +1,16 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
// ------------------------------------------------------------------------------
namespace ThingsGateway.Admin.Application;
public interface ISysHub
{
void UpdateVerificat(long clientId, long verificatId = 0, bool isConnect = true);
}

View File

@@ -8,17 +8,20 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.SignalR;
using BootstrapBlazor.Components;
namespace ThingsGateway.Admin.Application;
public class SignalrNoticeService : ISignalrNoticeService
{
private IHubContext<SysHub, ISysHub> HubContext;
public SignalrNoticeService(IHubContext<SysHub, ISysHub> hubContext)
private IEventService<SignalRMessage>? SignalRMessageDispatchService { get; set; }
private IEventService<string>? StringDispatchService { get; set; }
public SignalrNoticeService(IEventService<SignalRMessage> signalRMessageDispatchService,
IEventService<string> stringDispatchService
)
{
HubContext = hubContext;
SignalRMessageDispatchService = signalRMessageDispatchService;
StringDispatchService = stringDispatchService;
}
/// <inheritdoc/>
@@ -26,7 +29,12 @@ public class SignalrNoticeService : ISignalrNoticeService
{
//发送消息给用户
if (clientIds != null)
await HubContext.Clients.Users(clientIds.Select(a => a.ToString())).NewMessage(message);
{
foreach (var clientId in clientIds)
{
await SignalRMessageDispatchService.Publish(clientId.ToString(), message);
}
}
}
/// <inheritdoc/>
@@ -34,6 +42,11 @@ public class SignalrNoticeService : ISignalrNoticeService
{
//发送消息给用户
if (clientIds != null)
await HubContext.Clients.Users(clientIds.Select(a => a.ToString())).LoginOut(message);
{
foreach (var clientId in clientIds)
{
await StringDispatchService.Publish(clientId.ToString(), message);
}
}
}
}

View File

@@ -8,14 +8,11 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.SignalR;
using ThingsGateway.Core.Extension;
namespace ThingsGateway.Admin.Application;
public class SysHub : Hub<ISysHub>
public class SysHub : ISysHub
{
private readonly IVerificatInfoService _verificatInfoService;
@@ -24,63 +21,36 @@ public class SysHub : Hub<ISysHub>
_verificatInfoService = verificatInfoService;
}
/// <summary>
/// 连接
/// </summary>
public override async Task OnConnectedAsync()
{
var feature = Context.Features.Get<IHttpContextFeature>();
var VerificatId = feature!.HttpContext!.Request.Headers[ClaimConst.VerificatId].FirstOrDefault().ToLong();
if (VerificatId > 0)
{
var userIdentifier = Context.UserIdentifier;//自定义的Id
UpdateVerificat(userIdentifier.ToLong(), verificat: VerificatId);//更新cache
}
await base.OnConnectedAsync();
}
/// <summary>
/// 断开连接
/// </summary>
public override async Task OnDisconnectedAsync(Exception? exception)
{
var userIdentifier = Context.UserIdentifier.ToLong();//自定义的Id
var feature = Context.Features.Get<IHttpContextFeature>();
var VerificatId = feature!.HttpContext!.Request.Headers[ClaimConst.VerificatId].FirstOrDefault().ToLong();
UpdateVerificat(userIdentifier, verificat: VerificatId, isConnect: false);//更新cache
await base.OnDisconnectedAsync(exception);
}
#region
/// <summary>
/// 更新cache
/// </summary>
/// <param name="userId">用户id</param>
/// <param name="verificat">上线时的验证id</param>
/// <param name="clientId">用户id</param>
/// <param name="verificatId">上线时的验证id</param>
/// <param name="isConnect">上线</param>
private void UpdateVerificat(long userId, long verificat = 0, bool isConnect = true)
public void UpdateVerificat(long clientId, long verificatId = 0, bool isConnect = true)
{
if (userId != 0)
if (clientId != 0)
{
//获取cache当前用户的verificat信息列表
if (isConnect)
{
//获取cache中当前verificat
var verificatInfo = _verificatInfoService.GetOne(verificat);
var verificatInfo = _verificatInfoService.GetOne(verificatId);
if (verificatInfo != null)
{
verificatInfo.ClientIds.Add(userId);//添加到客户端列表
verificatInfo.ClientIds.Add(clientId);//添加到客户端列表
_verificatInfoService.Update(verificatInfo);//更新Cache
}
}
else
{
//获取当前客户端ID所在的verificat信息
var verificatInfo = _verificatInfoService.GetOne(verificat);
var verificatInfo = _verificatInfoService.GetOne(verificatId);
if (verificatInfo != null)
{
verificatInfo.ClientIds.RemoveWhere(it => it == userId);//从客户端列表删除
verificatInfo.ClientIds.RemoveWhere(it => it == clientId);//从客户端列表删除
_verificatInfoService.Update(verificatInfo);//更新Cache
}
}
@@ -88,4 +58,5 @@ public class SysHub : Hub<ISysHub>
}
#endregion
}

View File

@@ -73,7 +73,7 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
public async Task<SysUser?> GetUserByIdAsync(long userId)
{
//先从Cache拿
var sysUser = App.CacheService.HashGetOne<SysUser>(CacheConst.Cache_SysUser, userId.ToString());
var sysUser = NetCoreApp.CacheService.HashGetOne<SysUser>(CacheConst.Cache_SysUser, userId.ToString());
sysUser ??= await GetUserFromDb(userId);//从数据库拿用户信息
return sysUser;
}
@@ -86,7 +86,7 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
public async Task<long> GetIdByAccountAsync(string account)
{
//先从Cache拿
var userId = App.CacheService.HashGetOne<long>(CacheConst.Cache_SysUserAccount, account);
var userId = NetCoreApp.CacheService.HashGetOne<long>(CacheConst.Cache_SysUserAccount, account);
if (userId == 0)
{
//单查获取用户账号对应ID
@@ -95,7 +95,7 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
if (userId != 0)
{
//插入Cache
App.CacheService.HashAdd(CacheConst.Cache_SysUserAccount, account, userId);
NetCoreApp.CacheService.HashAdd(CacheConst.Cache_SysUserAccount, account, userId);
}
}
return userId;
@@ -552,15 +552,15 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
public void DeleteUserFromCache(IEnumerable<long> ids)
{
var userIds = ids.Select(it => it.ToString()).ToArray();//id转string列表
var sysUsers = App.CacheService.HashGet<SysUser>(CacheConst.Cache_SysUser, userIds);//获取用户列表
var sysUsers = NetCoreApp.CacheService.HashGet<SysUser>(CacheConst.Cache_SysUser, userIds);//获取用户列表
if (sysUsers.Any())
{
var accounts = sysUsers.Where(it => it != null).Select(it => it.Account);//账号集合
var phones = sysUsers.Select(it => it.Phone);//手机号集合
//删除用户信息
App.CacheService.HashDel<SysUser>(CacheConst.Cache_SysUser, userIds);
NetCoreApp.CacheService.HashDel<SysUser>(CacheConst.Cache_SysUser, userIds);
//删除账号
App.CacheService.HashDel<long>(CacheConst.Cache_SysUserAccount, accounts.ToArray());
NetCoreApp.CacheService.HashDel<long>(CacheConst.Cache_SysUserAccount, accounts.ToArray());
}
}
@@ -677,8 +677,8 @@ public class SysUserService : BaseService<SysUser>, ISysUserService
}
//插入Cache
App.CacheService.HashAdd(CacheConst.Cache_SysUserAccount, sysUser.Account, sysUser.Id);
App.CacheService.HashAdd(CacheConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);
NetCoreApp.CacheService.HashAdd(CacheConst.Cache_SysUserAccount, sysUser.Account, sysUser.Id);
NetCoreApp.CacheService.HashAdd(CacheConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);
return sysUser;
}

View File

@@ -14,6 +14,7 @@ using Microsoft.Extensions.Configuration;
using System.Text.RegularExpressions;
using ThingsGateway.Core;
using ThingsGateway.Core.Extension;
using ThingsGateway.Core.Json.Extension;
@@ -161,7 +162,7 @@ public class UserCenterService : BaseService<SysUser>, IUserCenterService
[OperDesc("UpdatePassword", isRecordPar: false)]
public async Task UpdatePasswordAsync(UpdatePasswordInput input)
{
var websiteOptions = App.Configuration?.GetSection(nameof(WebsiteOptions)).Get<WebsiteOptions>()!;
var websiteOptions = NetCoreApp.Configuration?.GetSection(nameof(WebsiteOptions)).Get<WebsiteOptions>()!;
if (websiteOptions.Demo)
{
throw Oops.Bah(Localizer["DemoCanotUpdatePassword"]);

View File

@@ -26,7 +26,7 @@ public class VerificatInfoService : BaseService<VerificatInfo>, IVerificatInfoSe
public VerificatInfo GetOne(long id)
{
//先从Cache拿
var verificatInfo = App.CacheService.HashGetOne<VerificatInfo>(CacheConst.Cache_Token, id.ToString());
var verificatInfo = NetCoreApp.CacheService.HashGetOne<VerificatInfo>(CacheConst.Cache_Token, id.ToString());
verificatInfo ??= GetFromDb(id);
if (verificatInfo != null)
if (verificatInfo.VerificatTimeout.AddSeconds(30) < DateTime.Now)
@@ -48,7 +48,7 @@ public class VerificatInfoService : BaseService<VerificatInfo>, IVerificatInfoSe
private void SetCahce(VerificatInfo verificatInfo)
{
App.CacheService.HashAdd<VerificatInfo>(CacheConst.Cache_Token, verificatInfo.Id.ToString(), verificatInfo);
NetCoreApp.CacheService.HashAdd<VerificatInfo>(CacheConst.Cache_Token, verificatInfo.Id.ToString(), verificatInfo);
}
public List<VerificatInfo>? GetListByUserId(long userId)
@@ -176,12 +176,12 @@ public class VerificatInfoService : BaseService<VerificatInfo>, IVerificatInfoSe
private void RemoveCache()
{
App.CacheService.Remove(CacheConst.Cache_Token);
NetCoreApp.CacheService.Remove(CacheConst.Cache_Token);
}
private void RemoveCache(long id)
{
App.CacheService.HashDel<VerificatInfo>(CacheConst.Cache_Token, id.ToString());
NetCoreApp.CacheService.HashDel<VerificatInfo>(CacheConst.Cache_Token, id.ToString());
}
}

View File

@@ -11,12 +11,19 @@
using SqlSugar;
using ThingsGateway.Admin.Application;
using Yitter.IdGenerator;
namespace ThingsGateway;
public class WebSugarAopService : ISugarAopService
{
private IAppService _appService;
public WebSugarAopService(IAppService appService)
{
_appService = appService;
}
/// <summary>
/// Aop设置
/// </summary>
@@ -83,7 +90,7 @@ public class WebSugarAopService : ISugarAopService
if (entityInfo.PropertyName == nameof(BaseEntity.CreateTime))
entityInfo.SetValue(DateTime.Now);
if (App.User != null)
if (_appService.User != null)
{
//创建人
if (entityInfo.PropertyName == nameof(BaseEntity.CreateUserId))
@@ -99,7 +106,7 @@ public class WebSugarAopService : ISugarAopService
if (entityInfo.PropertyName == nameof(BaseEntity.UpdateTime))
entityInfo.SetValue(DateTime.Now);
//更新人
if (App.User != null)
if (_appService.User != null)
{
if (entityInfo.PropertyName == nameof(BaseEntity.UpdateUserId))
entityInfo.SetValue(UserManager.UserId);

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