Compare commits
	
		
			154 Commits
		
	
	
		
			10.9.66.0
			...
			2f58f7b47c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2f58f7b47c | ||
|   | 576c5207b7 | ||
|   | 2e83c19e0c | ||
|   | b22e90609b | ||
|   | 35edd7dc43 | ||
|   | bd178831e3 | ||
|   | fe9ec6ad10 | ||
|   | 67354e70b3 | ||
|   | 6f9ec2e24b | ||
|   | c0337e2b19 | ||
|   | 8a95f48f5a | ||
|   | 14f3c31265 | ||
|   | 1bad65378f | ||
|   | db3affc67e | ||
|   | 5ee8b50a92 | ||
|   | 301beda2a2 | ||
|   | 628b51a353 | ||
|   | f03445bc83 | ||
|   | 55a2ff5487 | ||
|   | 0fef7dcf3b | ||
|   | 19d9702606 | ||
|   | a8a9774932 | ||
|   | aad0f0e8c3 | ||
|   | e74eae50a7 | ||
|   | 3b16d7019f | ||
|   | 3e038028c2 | ||
|   | b1d8041f7e | ||
|   | 53a98b26cd | ||
|   | 42c740fa1b | ||
|   | 556819c90c | ||
|   | 2522333a9c | ||
|   | bd4ce7c09b | ||
|   | 156ed88bd6 | ||
|   | 2416226eb0 | ||
|   | 976323a716 | ||
|   | 3c9e397403 | ||
|   | 79406ad4a0 | ||
|   | 20c44f10ca | ||
|   | 31d6b2a9e6 | ||
|   | 68e5a9c546 | ||
|   | b2ea9f99b9 | ||
|   | 6d7d0e468a | ||
|   | ff1f632de2 | ||
|   | fc3d7015ee | ||
|   | 40c5acb522 | ||
|   | f9cc1cbb05 | ||
|   | cf6e8b58f0 | ||
|   | 615e3bb24c | ||
|   | 4a7534b210 | ||
|   | 58e099cb93 | ||
|   | a94a9c953c | ||
|   | 35e1ffa3e9 | ||
|   | 4921642151 | ||
|   | d71ee29da8 | ||
|   | 901aa2d59f | ||
|   | 764957c014 | ||
|   | 0e3898218b | ||
|   | 61f13cef3c | ||
|   | 0b663d9e01 | ||
|   | 6c95c6209f | ||
|   | 4d223d2622 | ||
|   | e8d7e91b64 | ||
|   | 8175f541ec | ||
|   | 0adbdb926b | ||
|   | 42adee9980 | ||
|   | 427a7404bc | ||
|   | 3658199e0a | ||
|   | 82eedee50a | ||
|   | 6a18fc3e06 | ||
|   | c37e314ed6 | ||
|   | a937a85d90 | ||
|   | 35dd4ae9d3 | ||
|   | 0b829ac85c | ||
|   | aa247422d2 | ||
|   | 2e00e8c135 | ||
|   | 34dd2cf0a7 | ||
|   | 8404e20c5e | ||
|   | 662aa162e9 | ||
|   | 5927738c32 | ||
|   | 3c9f97a5c3 | ||
|   | 179ca0aa0e | ||
|   | fc09a52da1 | ||
|   | 5436b91c89 | ||
|   | d1c46f51a6 | ||
|   | 4539d8d198 | ||
|   | 9ea9529a5f | ||
|   | 4e6be23aac | ||
|   | 2fabbd236b | ||
|   | 163a66530e | ||
|   | 29073a00c4 | ||
|   | c6d4d1ecfa | ||
|   | ba16889cad | ||
|   | 5aaed35b0f | ||
|   | df067c91eb | ||
|   | 2078b4a60b | ||
|   | 20a2e3ff8e | ||
|   | 61a973b1b5 | ||
|   | cbd72e2081 | ||
|   | 4e0377b20c | ||
|   | fd318d3cdc | ||
|   | 515bdb9700 | ||
|   | 46c1780017 | ||
|   | fe78a4c3ca | ||
|   | 2d7effadf9 | ||
|   | 346c560f8b | ||
|   | 8e3bd89f61 | ||
|   | 6da142d080 | ||
|   | ff7d029e6f | ||
|   | 21b4695683 | ||
|   | 02ad494a26 | ||
|   | 280366e1b2 | ||
|   | 6660ce3e34 | ||
|   | 7499162c1a | ||
|   | 40208a5cd6 | ||
|   | fa347f4f68 | ||
|   | d7df6fc605 | ||
|   | eb4bb2fd48 | ||
|   | faa9858974 | ||
|   | 1b3d2dda49 | ||
|   | a8a9453611 | ||
|   | e84f42ce14 | ||
|   | 6f814cf6b8 | ||
|   | e36432e4e9 | ||
|   | ebd71e807b | ||
|   | 34000d8d7d | ||
|   | e785f6660c | ||
|   | 831c611797 | ||
|   | 453817ef86 | ||
|   | 8ce0b981c1 | ||
|   | 4e5c51b54c | ||
|   | 3cc9d31f28 | ||
|   | 10391f869b | ||
|   | fba0723a6d | ||
|   | 2db3f78f0c | ||
|   | badf61fe01 | ||
|   | d74e0952dc | ||
|   | fb1699ce80 | ||
|   | 44adddbcd4 | ||
|   | 0eab889452 | ||
|   | e14d39a459 | ||
|   | 7575264ede | ||
|   | 3e1a077b96 | ||
|   | a921cb8400 | ||
|   | 4de7c31ed7 | ||
|   | 08326a2cfd | ||
|   | e045de5acb | ||
|   | d3bef31aa6 | ||
|   | 347d4d6e5d | ||
|   | b006a066e1 | ||
|   | e331bc5d3c | ||
|   | 2e81806231 | ||
|   | 37b48cf221 | ||
|   | 5997487434 | ||
|   | 4ce843182f | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -364,6 +364,5 @@ FodyWeavers.xsd | |||||||
|  |  | ||||||
| /src/*Pro*/ | /src/*Pro*/ | ||||||
| /src/*Pro* | /src/*Pro* | ||||||
| /src/*pro* |  | ||||||
| /src/*pro*/ |  | ||||||
| /src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json | /src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json | ||||||
|  | /src/.idea/ | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
| 		<IncludeBuildOutput>false</IncludeBuildOutput> | 		<IncludeBuildOutput>false</IncludeBuildOutput> | ||||||
| 		<!-- 避免 DLL 被打包到 lib/ --> | 		<!-- 避免 DLL 被打包到 lib/ --> | ||||||
| 		<EnableSourceGenerator>true</EnableSourceGenerator> | 		<EnableSourceGenerator>true</EnableSourceGenerator> | ||||||
|  | 		 | ||||||
| 		<!-- 可选 --> | 		<!-- 可选 --> | ||||||
|  |  | ||||||
| 		 | 		 | ||||||
| @@ -26,6 +27,6 @@ | |||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" PrivateAssets="all" Private="false" /> | 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" Private="false" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -122,6 +122,7 @@ using Microsoft.AspNetCore.Components; | |||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | #pragma warning disable CA1849 | ||||||
| #pragma warning disable CA2007 | #pragma warning disable CA2007 | ||||||
| #pragma warning disable CS0162 | #pragma warning disable CS0162 | ||||||
| #pragma warning disable CS8632 | #pragma warning disable CS8632 | ||||||
| @@ -276,6 +277,7 @@ namespace {namespaceName} | |||||||
| #pragma warning restore CS8632 | #pragma warning restore CS8632 | ||||||
| #pragma warning restore CS0162 | #pragma warning restore CS0162 | ||||||
| #pragma warning restore CA2007 | #pragma warning restore CA2007 | ||||||
|  | #pragma warning restore CA1849 | ||||||
| "); | "); | ||||||
|         var bases = class_symbol.GetTypeHierarchy().Where(t => !SymbolEqualityComparer.Default.Equals(t, class_symbol)); |         var bases = class_symbol.GetTypeHierarchy().Where(t => !SymbolEqualityComparer.Default.Equals(t, class_symbol)); | ||||||
|         var members = class_symbol.GetMembers() // members of the type itself |         var members = class_symbol.GetMembers() // members of the type itself | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
|  |  | ||||||
| <div align="center"><h1 align="center">ThingsBlazor</a></h1></div> | <div align="center"><h1 align="center">ThingsBlazor</h1></div> | ||||||
| <div align="center"><h3 align="center">权限管理框架</h3></div> | <div align="center"><h3 align="center">权限管理框架</h3></div> | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,9 +38,9 @@ public sealed class OperDescAttribute : MoAttribute | |||||||
|  |  | ||||||
|     static OperDescAttribute() |     static OperDescAttribute() | ||||||
|     { |     { | ||||||
|         // 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中 |  | ||||||
|         Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning); |  | ||||||
|         AppService = App.RootServices.GetService<IAppService>(); |         AppService = App.RootServices.GetService<IAppService>(); | ||||||
|  |         // 创建长时间运行的后台任务,并将日志消息队列中数据写入存储中 | ||||||
|  |         Task.Factory.StartNew(ProcessQueueAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null) |     public OperDescAttribute(string description, bool isRecordPar = true, object localizerType = null) | ||||||
| @@ -93,7 +93,7 @@ public sealed class OperDescAttribute : MoAttribute | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 将日志消息写入数据库中 |     /// 将日志消息写入数据库中 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static async Task ProcessQueue() |     private static async Task ProcessQueueAsync() | ||||||
|     { |     { | ||||||
|         var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!; |         var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!; | ||||||
|         while (!appLifetime.ApplicationStopping.IsCancellationRequested) |         while (!appLifetime.ApplicationStopping.IsCancellationRequested) | ||||||
|   | |||||||
| @@ -34,17 +34,20 @@ public class FileController : ControllerBase | |||||||
|             return BadRequest("Invalid file name."); |             return BadRequest("Invalid file name."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName); |         var root = Directory.GetCurrentDirectory(); | ||||||
|  |         var wwwroot = Path.Combine(root, "wwwroot"); | ||||||
|         if (!System.IO.File.Exists(filePath)) |         var filePath = Path.Combine(wwwroot, fileName); | ||||||
|  | #pragma warning disable CA3003 | ||||||
|  |         if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath)) | ||||||
|         { |         { | ||||||
|             return NotFound(); |             return NotFound(); | ||||||
|         } |         } | ||||||
|  | #pragma warning restore CA3003 | ||||||
|  |  | ||||||
|         var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); |         var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); | ||||||
|  |  | ||||||
|         Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition"); |         Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition"); | ||||||
|  |  | ||||||
|         return File(fileStream, "application/octet-stream", (fileName.Replace('/', '_'))); |         return File(fileStream, "application/octet-stream", (Path.GetFileName(filePath).Replace('/', '_'))); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -38,12 +38,14 @@ public class VerificatInfo : PrimaryIdEntity | |||||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true)] |     [AutoGenerateColumn(Filterable = true, Sortable = true)] | ||||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] |     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||||
|     [IgnoreExcel] |     [IgnoreExcel] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public override long Id { get; set; } |     public override long Id { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 登录IP |     /// 登录IP | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)] |     [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)] | ||||||
|  |     [SugarColumn(IsNullable = true)] | ||||||
|     public string LoginIp { get; set; } |     public string LoginIp { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -77,5 +79,6 @@ public class VerificatInfo : PrimaryIdEntity | |||||||
|     /// 登录设备 |     /// 登录设备 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)] |     [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)] | ||||||
|  |     [SugarColumn(IsNullable = true)] | ||||||
|     public string Device { get; set; } |     public string Device { get; set; } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter | |||||||
|  |  | ||||||
|         if (exception == null) |         if (exception == null) | ||||||
|         { |         { | ||||||
|             logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); |             if (logger.IsEnabled(LogLevel.Information)) | ||||||
|  |                 logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); |             if (logger.IsEnabled(LogLevel.Warning)) | ||||||
|  |                 logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,16 +21,7 @@ | |||||||
|     "UserNoModule": "This account has not been assigned a module. Please contact the administrator", |     "UserNoModule": "This account has not been assigned a module. Please contact the administrator", | ||||||
|     "UserNull": "User {0} does not exist" |     "UserNull": "User {0} does not exist" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Admin.Application.BaseDataEntity": { |    | ||||||
|     "CreateOrgId": "CreateOrgId" |  | ||||||
|   }, |  | ||||||
|   "ThingsGateway.Admin.Application.BaseEntity": { |  | ||||||
|     "CreateTime": "CreateTime", |  | ||||||
|     "CreateUser": "CreateUser", |  | ||||||
|     "SortCode": "SortCode", |  | ||||||
|     "UpdateTime": "UpdateTime", |  | ||||||
|     "UpdateUser": "UpdateUser" |  | ||||||
|   }, |  | ||||||
|   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { |   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { | ||||||
|     "UserExpire": "User expired, please login again" |     "UserExpire": "User expired, please login again" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -21,16 +21,7 @@ | |||||||
|     "UserNoModule": "该账号未分配模块,请联系管理员", |     "UserNoModule": "该账号未分配模块,请联系管理员", | ||||||
|     "UserNull": "用户 {0} 不存在" |     "UserNull": "用户 {0} 不存在" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Admin.Application.BaseDataEntity": { |  | ||||||
|     "CreateOrgId": "创建机构Id" |  | ||||||
|   }, |  | ||||||
|   "ThingsGateway.Admin.Application.BaseEntity": { |  | ||||||
|     "CreateTime": "创建时间", |  | ||||||
|     "CreateUser": "创建人", |  | ||||||
|     "SortCode": "排序", |  | ||||||
|     "UpdateTime": "更新时间", |  | ||||||
|     "UpdateUser": "更新人" |  | ||||||
|   }, |  | ||||||
|   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { |   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { | ||||||
|     "UserExpire": "用户登录已过期,请重新登录" |     "UserExpire": "用户登录已过期,请重新登录" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
| using Riok.Mapperly.Abstractions; | using Riok.Mapperly.Abstractions; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Admin.Application; | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
| [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)] | [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)] | ||||||
| public static partial class AdminMapper | public static partial class AdminMapper | ||||||
| { | { | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Admin.Application; | ||||||
| 
 | 
 | ||||||
| public class USheetDatas | public class USheetDatas | ||||||
| { | { | ||||||
| @@ -30,7 +30,7 @@ public class AdminOAuthHandler<TOptions>( | |||||||
| { | { | ||||||
|     static AdminOAuthHandler() |     static AdminOAuthHandler() | ||||||
|     { |     { | ||||||
|         Task.Factory.StartNew(Insertable, TaskCreationOptions.LongRunning); |         Task.Factory.StartNew(InsertableAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -41,7 +41,7 @@ public class AdminOAuthHandler<TOptions>( | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 创建访问日志 |     /// 创建访问日志 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static async Task Insertable() |     private static async Task InsertableAsync() | ||||||
|     { |     { | ||||||
|         var db = DbContext.GetDB<SysOperateLog>(); |         var db = DbContext.GetDB<SysOperateLog>(); | ||||||
|         var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!; |         var appLifetime = App.RootServices!.GetService<IHostApplicationLifetime>()!; | ||||||
| @@ -145,7 +145,7 @@ public class AdminOAuthHandler<TOptions>( | |||||||
|         var loginEvent = new LoginEvent |         var loginEvent = new LoginEvent | ||||||
|         { |         { | ||||||
|             Ip = appService.RemoteIpAddress, |             Ip = appService.RemoteIpAddress, | ||||||
|             Device = appService.UserAgent?.Platform, |             Device = appService.UserAgent?.Platform ?? "Unknown", | ||||||
|             Expire = expire, |             Expire = expire, | ||||||
|             SysUser = sysUser, |             SysUser = sysUser, | ||||||
|             VerificatId = CommonUtils.GetSingleId() |             VerificatId = CommonUtils.GetSingleId() | ||||||
| @@ -156,7 +156,7 @@ public class AdminOAuthHandler<TOptions>( | |||||||
|         //生成verificat信息 |         //生成verificat信息 | ||||||
|         var verificatInfo = new VerificatInfo |         var verificatInfo = new VerificatInfo | ||||||
|         { |         { | ||||||
|             Device = loginEvent.Device, |             Device = loginEvent.Device ?? "Unknown", | ||||||
|             Expire = loginEvent.Expire, |             Expire = loginEvent.Expire, | ||||||
|             VerificatTimeout = tokenTimeout, |             VerificatTimeout = tokenTimeout, | ||||||
|             Id = loginEvent.VerificatId, |             Id = loginEvent.VerificatId, | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ | |||||||
|       "Module": 2, |       "Module": 2, | ||||||
|       "Title": "权限管理", |       "Title": "权限管理", | ||||||
|       "Code": "System", |       "Code": "System", | ||||||
|       "NavLinkMatch": "All", |       "NavLinkMatch": "Prefix", | ||||||
|       "Category": "MENU", |       "Category": "MENU", | ||||||
|       "Target": "_self", |       "Target": "_self", | ||||||
|       "Href": null, |       "Href": null, | ||||||
| @@ -47,7 +47,7 @@ | |||||||
|       "ParentId": 0, |       "ParentId": 0, | ||||||
|       "Module": 2, |       "Module": 2, | ||||||
|       "Title": "系统运维", |       "Title": "系统运维", | ||||||
|       "NavLinkMatch": "All", |       "NavLinkMatch": "Prefix", | ||||||
|       "Code": "System", |       "Code": "System", | ||||||
|       "Category": "MENU", |       "Category": "MENU", | ||||||
|       "Target": "_self", |       "Target": "_self", | ||||||
|   | |||||||
| @@ -235,7 +235,7 @@ public class AuthService : IAuthService | |||||||
|         var logingEvent = new LoginEvent |         var logingEvent = new LoginEvent | ||||||
|         { |         { | ||||||
|             Ip = _appService.RemoteIpAddress, |             Ip = _appService.RemoteIpAddress, | ||||||
|             Device = _appService.UserAgent?.Platform, |             Device = _appService.UserAgent?.Platform ?? "Unknown", | ||||||
|             Expire = expire, |             Expire = expire, | ||||||
|             SysUser = sysUser, |             SysUser = sysUser, | ||||||
|             VerificatId = verificatId |             VerificatId = verificatId | ||||||
| @@ -344,7 +344,7 @@ public class AuthService : IAuthService | |||||||
|         //生成verificat信息 |         //生成verificat信息 | ||||||
|         var verificatInfo = new VerificatInfo |         var verificatInfo = new VerificatInfo | ||||||
|         { |         { | ||||||
|             Device = loginEvent.Device, |             Device = loginEvent.Device ?? "Unknown", | ||||||
|             Expire = loginEvent.Expire, |             Expire = loginEvent.Expire, | ||||||
|             VerificatTimeout = tokenTimeout, |             VerificatTimeout = tokenTimeout, | ||||||
|             Id = loginEvent.VerificatId, |             Id = loginEvent.VerificatId, | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application; | |||||||
| /// <typeparam name="TEntry"></typeparam> | /// <typeparam name="TEntry"></typeparam> | ||||||
| public class EventService<TEntry> : IEventService<TEntry>, IDisposable | public class EventService<TEntry> : IEventService<TEntry>, IDisposable | ||||||
| { | { | ||||||
|     private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new(); |     private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new(); | ||||||
|  |  | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| namespace ThingsGateway.Admin.Application; | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
| internal sealed class NoticeService : INoticeService | internal sealed class NoticeService : INoticeService | ||||||
| { | { | ||||||
|     private IEventService<AppMessage>? MessageDispatchService { get; set; } |     private IEventService<AppMessage>? MessageDispatchService { get; set; } | ||||||
|   | |||||||
| @@ -128,18 +128,19 @@ internal sealed class SysOrgService : BaseService<SysOrg>, ISysOrgService | |||||||
|     [OperDesc("DeleteOrg")] |     [OperDesc("DeleteOrg")] | ||||||
|     public async Task<bool> DeleteOrgAsync(IEnumerable<long> ids) |     public async Task<bool> DeleteOrgAsync(IEnumerable<long> ids) | ||||||
|     { |     { | ||||||
|  |         var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合 | ||||||
|  |         var sysOrgList = await GetAllAsync().ConfigureAwait(false);//获取所有组织 | ||||||
|  |         foreach (var it in ids) | ||||||
|  |         { | ||||||
|  |             var children = SysOrgService.GetSysOrgChildren(sysOrgList, it);//查找下级组织 | ||||||
|  |             sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList()); | ||||||
|  |             sysDeleteOrgList.Add(it); | ||||||
|  |         } | ||||||
|         //获取所有ID |         //获取所有ID | ||||||
|         if (ids.Any()) |         if (sysDeleteOrgList.Count != 0) | ||||||
|         { |         { | ||||||
|             using var db = GetDB(); |             using var db = GetDB(); | ||||||
|             var sysOrgList = await GetAllAsync().ConfigureAwait(false);//获取所有组织 |  | ||||||
|             var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合 |  | ||||||
|             foreach (var it in ids) |  | ||||||
|             { |  | ||||||
|                 var children = SysOrgService.GetSysOrgChildren(sysOrgList, it);//查找下级组织 |  | ||||||
|                 sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList()); |  | ||||||
|                 sysDeleteOrgList.Add(it); |  | ||||||
|             } |  | ||||||
|             //如果组织下有用户则不能删除 |             //如果组织下有用户则不能删除 | ||||||
|             if (await db.Queryable<SysUser>().AnyAsync(it => sysDeleteOrgList.Contains(it.OrgId)).ConfigureAwait(false)) |             if (await db.Queryable<SysUser>().AnyAsync(it => sysDeleteOrgList.Contains(it.OrgId)).ConfigureAwait(false)) | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ public class SysPositionService : BaseService<SysPosition>, ISysPositionService | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id)); |             var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id)); | ||||||
|             await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId).ToList(), dels.Select(a => a.CreateUserId).ToList()).ConfigureAwait(false); |             await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId), dels.Select(a => a.CreateUserId)).ConfigureAwait(false); | ||||||
|             //删除职位 |             //删除职位 | ||||||
|             var result = await base.DeleteAsync(ids).ConfigureAwait(false); |             var result = await base.DeleteAsync(ids).ConfigureAwait(false); | ||||||
|             if (result) |             if (result) | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ public interface ISysResourceService | |||||||
|     /// <param name="resourceList">资源列表</param> |     /// <param name="resourceList">资源列表</param> | ||||||
|     /// <param name="parentId">父ID</param> |     /// <param name="parentId">父ID</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     IEnumerable<SysResource> ConstructMenuTrees(IEnumerable<SysResource> resourceList, long parentId = 0); |     IEnumerable<SysResource> ConstructMenuTrees(List<SysResource> resourceList, long parentId = 0); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 复制资源到其他模块 |     /// 复制资源到其他模块 | ||||||
| @@ -44,7 +44,7 @@ public interface ISysResourceService | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="ids">id列表</param> |     /// <param name="ids">id列表</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     Task<bool> DeleteResourceAsync(IEnumerable<long> ids); |     Task<bool> DeleteResourceAsync(HashSet<long> ids); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从缓存/数据库读取全部资源列表 |     /// 从缓存/数据库读取全部资源列表 | ||||||
|   | |||||||
| @@ -75,10 +75,10 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc | |||||||
|     /// <param name="ids">id列表</param> |     /// <param name="ids">id列表</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [OperDesc("DeleteResource")] |     [OperDesc("DeleteResource")] | ||||||
|     public async Task<bool> DeleteResourceAsync(IEnumerable<long> ids) |     public async Task<bool> DeleteResourceAsync(HashSet<long> ids) | ||||||
|     { |     { | ||||||
|         //删除 |         //删除 | ||||||
|         if (ids.Any()) |         if (ids.Count != 0) | ||||||
|         { |         { | ||||||
|             //获取所有菜单和按钮 |             //获取所有菜单和按钮 | ||||||
|             var resourceList = await GetAllAsync().ConfigureAwait(false); |             var resourceList = await GetAllAsync().ConfigureAwait(false); | ||||||
| @@ -86,10 +86,11 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc | |||||||
|             var delSysResources = resourceList.Where(it => ids.Contains(it.Id)); |             var delSysResources = resourceList.Where(it => ids.Contains(it.Id)); | ||||||
|             //找到要删除的模块 |             //找到要删除的模块 | ||||||
|             var delModules = resourceList.Where(a => a.Category == ResourceCategoryEnum.Module).Where(it => ids.Contains(it.Id)); |             var delModules = resourceList.Where(a => a.Category == ResourceCategoryEnum.Module).Where(it => ids.Contains(it.Id)); | ||||||
|             if (delModules.Any()) |  | ||||||
|  |             //获取模块下的所有列表 | ||||||
|  |             var delHashSet = delModules.Select(a => a.Id).ToHashSet(); | ||||||
|  |             if (delHashSet.Count != 0) | ||||||
|             { |             { | ||||||
|                 //获取模块下的所有列表 |  | ||||||
|                 var delHashSet = delModules.Select(a => a.Id).ToHashSet(); |  | ||||||
|                 var delModuleResources = resourceList.Where(it => delHashSet.Contains(it.Module)); |                 var delModuleResources = resourceList.Where(it => delHashSet.Contains(it.Module)); | ||||||
|                 delSysResources = delSysResources.Concat(delModuleResources).ToHashSet(); |                 delSysResources = delSysResources.Concat(delModuleResources).ToHashSet(); | ||||||
|             } |             } | ||||||
| @@ -345,17 +346,14 @@ internal sealed class SysResourceService : BaseService<SysResource>, ISysResourc | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IEnumerable<SysResource> ConstructMenuTrees(IEnumerable<SysResource> resourceList, long parentId = 0) |     public IEnumerable<SysResource> ConstructMenuTrees(List<SysResource> resourceList, long parentId = 0) | ||||||
|     { |     { | ||||||
|         //找下级资源ID列表 |         //找下级资源ID列表 | ||||||
|         var resources = resourceList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode); |         var resources = resourceList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode); | ||||||
|         if (resources.Any())//如果数量大于0 |         foreach (var item in resources)//遍历资源 | ||||||
|         { |         { | ||||||
|             foreach (var item in resources)//遍历资源 |             var children = ConstructMenuTrees(resourceList, item.Id).ToList();//添加子节点 | ||||||
|             { |             item.Children = children.Count > 0 ? children : null; | ||||||
|                 var children = ConstructMenuTrees(resourceList, item.Id).ToList();//添加子节点 |  | ||||||
|                 item.Children = children.Count > 0 ? children : null; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         return resources; |         return resources; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ public interface ISysRoleService | |||||||
|     /// 删除角色 |     /// 删除角色 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="ids">id列表</param> |     /// <param name="ids">id列表</param> | ||||||
|     Task<bool> DeleteRoleAsync(IEnumerable<long> ids); |     Task<bool> DeleteRoleAsync(HashSet<long> ids); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从缓存/数据库获取全部角色信息 |     /// 从缓存/数据库获取全部角色信息 | ||||||
| @@ -43,7 +43,7 @@ public interface ISysRoleService | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="input">角色id列表</param> |     /// <param name="input">角色id列表</param> | ||||||
|     /// <returns>角色列表</returns> |     /// <returns>角色列表</returns> | ||||||
|     Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(IEnumerable<long> input); |     Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(HashSet<long> input); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 根据用户id获取角色列表 |     /// 根据用户id获取角色列表 | ||||||
|   | |||||||
| @@ -61,42 +61,46 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|  |  | ||||||
|         var topOrgList = sysOrgList.Where(it => it.ParentId == 0);//获取顶级机构 |         var topOrgList = sysOrgList.Where(it => it.ParentId == 0);//获取顶级机构 | ||||||
|         var globalRole = sysRoles.Where(it => it.Category == RoleCategoryEnum.Global);//获取全局角色 |         var globalRole = sysRoles.Where(it => it.Category == RoleCategoryEnum.Global);//获取全局角色 | ||||||
|         if (globalRole.Any()) |         var children = globalRole.Select(it => new RoleTreeOutput | ||||||
|         { |         { | ||||||
|             result.Add(new RoleTreeOutput() |             Id = it.Id, | ||||||
|             { |             Name = it.Name, | ||||||
|                 Id = CommonUtils.GetSingleId(), |             IsRole = true | ||||||
|                 Name = Localizer["Global"], |         }).ToList(); | ||||||
|                 Children = globalRole.Select(it => new RoleTreeOutput |  | ||||||
|                 { |         result.Add(new RoleTreeOutput() | ||||||
|                     Id = it.Id, |         { | ||||||
|                     Name = it.Name, |             Id = CommonUtils.GetSingleId(), | ||||||
|                     IsRole = true |             Name = Localizer["Global"], | ||||||
|                 }).ToList() |             Children = children | ||||||
|             });//添加全局角色 |         });//添加全局角色 | ||||||
|         } |  | ||||||
|         //遍历顶级机构 |         //遍历顶级机构 | ||||||
|         foreach (var org in topOrgList) |         foreach (var org in topOrgList) | ||||||
|         { |         { | ||||||
|             var childIds = await _sysOrgService.GetOrgChildIdsAsync(org.Id, true, sysOrgList).ConfigureAwait(false);//获取机构下的所有子级ID |             var childIds = await _sysOrgService.GetOrgChildIdsAsync(org.Id, true, sysOrgList).ConfigureAwait(false);//获取机构下的所有子级ID | ||||||
|             var childRoles = sysRoles.Where(it => it.OrgId != 0 && childIds.Contains(it.OrgId));//获取机构下的所有角色 |             var childRoles = sysRoles.Where(it => it.OrgId != 0 && childIds.Contains(it.OrgId));//获取机构下的所有角色 | ||||||
|             if (childRoles.Any()) |  | ||||||
|  |             List<RoleTreeOutput> childrenRoleTreeOutputs = new(); | ||||||
|  |  | ||||||
|  |             foreach (var it in childRoles) | ||||||
|  |             { | ||||||
|  |                 childrenRoleTreeOutputs.Add(new RoleTreeOutput() | ||||||
|  |                 { | ||||||
|  |                     Id = it.Id, | ||||||
|  |                     Name = it.Name, | ||||||
|  |                     IsRole = true | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             if (childrenRoleTreeOutputs.Count > 0) | ||||||
|  |  | ||||||
|             { |             { | ||||||
|                 var roleTreeOutput = new RoleTreeOutput |                 var roleTreeOutput = new RoleTreeOutput | ||||||
|                 { |                 { | ||||||
|                     Id = org.Id, |                     Id = org.Id, | ||||||
|                     Name = org.Name, |                     Name = org.Name, | ||||||
|                     IsRole = false |                     IsRole = false, | ||||||
|  |                     Children = childrenRoleTreeOutputs | ||||||
|                 };//实例化角色树 |                 };//实例化角色树 | ||||||
|                 foreach (var it in childRoles) |  | ||||||
|                 { |  | ||||||
|                     roleTreeOutput.Children.Add(new RoleTreeOutput() |  | ||||||
|                     { |  | ||||||
|                         Id = it.Id, |  | ||||||
|                         Name = it.Name, |  | ||||||
|                         IsRole = true |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|                 result.Add(roleTreeOutput); |                 result.Add(roleTreeOutput); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -147,7 +151,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="input">角色id列表</param> |     /// <param name="input">角色id列表</param> | ||||||
|     /// <returns>角色列表</returns> |     /// <returns>角色列表</returns> | ||||||
|     public async Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(IEnumerable<long> input) |     public async Task<IEnumerable<SysRole>> GetRoleListByIdListAsync(HashSet<long> input) | ||||||
|     { |     { | ||||||
|         var roles = await GetAllAsync().ConfigureAwait(false); |         var roles = await GetAllAsync().ConfigureAwait(false); | ||||||
|         var roleList = roles.Where(it => input.Contains(it.Id)); |         var roleList = roles.Where(it => input.Contains(it.Id)); | ||||||
| @@ -162,7 +166,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="ids">id列表</param> |     /// <param name="ids">id列表</param> | ||||||
|     [OperDesc("DeleteRole")] |     [OperDesc("DeleteRole")] | ||||||
|     public async Task<bool> DeleteRoleAsync(IEnumerable<long> ids) |     public async Task<bool> DeleteRoleAsync(HashSet<long> ids) | ||||||
|     { |     { | ||||||
|         var sysRoles = await GetAllAsync().ConfigureAwait(false);//获取所有角色 |         var sysRoles = await GetAllAsync().ConfigureAwait(false);//获取所有角色 | ||||||
|         var hasSuperAdmin = sysRoles.Any(it => it.Id == RoleConst.SuperAdminRoleId && ids.Contains(it.Id));//判断是否有超级管理员 |         var hasSuperAdmin = sysRoles.Any(it => it.Id == RoleConst.SuperAdminRoleId && ids.Contains(it.Id));//判断是否有超级管理员 | ||||||
| @@ -170,10 +174,10 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|             throw Oops.Bah(Localizer["CanotDeleteAdmin"]); |             throw Oops.Bah(Localizer["CanotDeleteAdmin"]); | ||||||
|  |  | ||||||
|         var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id)); |         var dels = (await GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id)); | ||||||
|         await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId).ToList(), dels.Select(a => a.CreateUserId).ToList()).ConfigureAwait(false); |         await SysUserService.CheckApiDataScopeAsync(dels.Select(a => a.OrgId), dels.Select(a => a.CreateUserId)).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         //数据库是string所以这里转下 |         //数据库是string所以这里转下 | ||||||
|         var targetIds = ids.Select(it => it.ToString()); |         var targetIds = ids.Select(it => it.ToString()).ToList(); | ||||||
|         //定义删除的关系 |         //定义删除的关系 | ||||||
|         var delRelations = new List<RelationCategoryEnum> { |         var delRelations = new List<RelationCategoryEnum> { | ||||||
|             RelationCategoryEnum.RoleHasResource, |             RelationCategoryEnum.RoleHasResource, | ||||||
| @@ -184,7 +188,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|         //事务 |         //事务 | ||||||
|         var result = await db.UseTranAsync(async () => |         var result = await db.UseTranAsync(async () => | ||||||
|         { |         { | ||||||
|             await db.Deleteable<SysRole>().In(ids.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除 |             await db.Deleteable<SysRole>().In(ids).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除 | ||||||
|             //删除关系表角色与资源关系,角色与权限关系 |             //删除关系表角色与资源关系,角色与权限关系 | ||||||
|             await db.Deleteable<SysRelation>(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync().ConfigureAwait(false); |             await db.Deleteable<SysRelation>(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|             //删除关系表角色与用户关系 |             //删除关系表角色与用户关系 | ||||||
| @@ -278,7 +282,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|         if (sysRole != null) |         if (sysRole != null) | ||||||
|         { |         { | ||||||
|             var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false); |             var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false); | ||||||
|             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu).Where(a => menuIds.Contains(a.Id)); |             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu && menuIds.Contains(a.Id)); | ||||||
|  |  | ||||||
|             #region 角色模块处理 |             #region 角色模块处理 | ||||||
|  |  | ||||||
| @@ -317,7 +321,7 @@ internal sealed class SysRoleService : BaseService<SysRole>, ISysRoleService | |||||||
|             #region 角色权限处理. |             #region 角色权限处理. | ||||||
|             var defaultDataScope = sysRole.DefaultDataScope;//获取默认数据范围 |             var defaultDataScope = sysRole.DefaultDataScope;//获取默认数据范围 | ||||||
|  |  | ||||||
|             if (menusList.Any()) |             if (relationRoles.Count != 0) | ||||||
|             { |             { | ||||||
|                 //获取权限授权树 |                 //获取权限授权树 | ||||||
|                 var permissions = App.GetService<IApiPermissionService>().PermissionTreeSelector(menusList.Select(it => it.Href)); |                 var permissions = App.GetService<IApiPermissionService>().PermissionTreeSelector(menusList.Select(it => it.Href)); | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 主键Id |     /// 主键Id | ||||||
|     /// </summary> |     /// </summary> | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public override long Id { get; set; } |     public override long Id { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ public interface ISysUserService | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="ids">用户ID列表。</param> |     /// <param name="ids">用户ID列表。</param> | ||||||
|     /// <returns>是否删除成功。</returns> |     /// <returns>是否删除成功。</returns> | ||||||
|     Task<bool> DeleteUserAsync(IEnumerable<long> ids); |     Task<bool> DeleteUserAsync(HashSet<long> ids); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从缓存中删除用户信息。 |     /// 从缓存中删除用户信息。 | ||||||
|   | |||||||
| @@ -377,9 +377,9 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|     /// 获取用户拥有的资源 |     /// 获取用户拥有的资源 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="id">用户id</param> |     /// <param name="id">用户id</param> | ||||||
|     public async Task<GrantResourceData> OwnResourceAsync(long id) |     public Task<GrantResourceData> OwnResourceAsync(long id) | ||||||
|     { |     { | ||||||
|         return await _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource).ConfigureAwait(false); |         return _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -505,10 +505,10 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|         var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下 |         var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下 | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         //重置密码 |         //重置密码 | ||||||
|         if (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser |         if ((await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser | ||||||
|         { |         { | ||||||
|             Password = password |             Password = password | ||||||
|         }, it => it.Id == id).ConfigureAwait(false)) |         }, it => it.Id == id).ConfigureAwait(false)) > 0) | ||||||
|         { |         { | ||||||
|             DeleteUserFromCache(id);//从cache删除用户信息 |             DeleteUserFromCache(id);//从cache删除用户信息 | ||||||
|             var verificatInfoIds = _verificatInfoService.GetListByUserId(id); |             var verificatInfoIds = _verificatInfoService.GetListByUserId(id); | ||||||
| @@ -550,7 +550,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|         if (sysUser != null) |         if (sysUser != null) | ||||||
|         { |         { | ||||||
|             var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false); |             var resources = await _sysResourceService.GetAllAsync().ConfigureAwait(false); | ||||||
|             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu).Where(a => menuIds.Contains(a.Id)); |             var menusList = resources.Where(a => a.Category == ResourceCategoryEnum.Menu && menuIds.Contains(a.Id)); | ||||||
|  |  | ||||||
|             #region 用户模块处理 |             #region 用户模块处理 | ||||||
|  |  | ||||||
| @@ -587,7 +587,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|             #region 用户权限处理. |             #region 用户权限处理. | ||||||
|  |  | ||||||
|             //获取菜单信息 |             //获取菜单信息 | ||||||
|             if (menusList.Any()) |             if (relationUsers.Count != 0) | ||||||
|             { |             { | ||||||
|                 //获取权限授权树 |                 //获取权限授权树 | ||||||
|                 var permissions = App.GetService<IApiPermissionService>().PermissionTreeSelector(menusList.Select(it => it.Href)); |                 var permissions = App.GetService<IApiPermissionService>().PermissionTreeSelector(menusList.Select(it => it.Href)); | ||||||
| @@ -642,7 +642,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     [OperDesc("DeleteUser")] |     [OperDesc("DeleteUser")] | ||||||
|     public async Task<bool> DeleteUserAsync(IEnumerable<long> ids) |     public async Task<bool> DeleteUserAsync(HashSet<long> ids) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         var containsSuperAdmin = await db.Queryable<SysUser>().Where(it => it.Id == RoleConst.SuperAdminId && ids.Contains(it.Id)).AnyAsync().ConfigureAwait(false);//判断是否有超管 |         var containsSuperAdmin = await db.Queryable<SysUser>().Where(it => it.Id == RoleConst.SuperAdminId && ids.Contains(it.Id)).AnyAsync().ConfigureAwait(false);//判断是否有超管 | ||||||
| @@ -672,7 +672,7 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|             .ExecuteCommandAsync().ConfigureAwait(false); |             .ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|             //删除用户 |             //删除用户 | ||||||
|             await db.Deleteable<SysUser>().In(ids.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除 |             await db.Deleteable<SysUser>().In(ids).ExecuteCommandHasChangeAsync().ConfigureAwait(false);//删除 | ||||||
|  |  | ||||||
|             //删除关系表用户与资源关系,用户与权限关系,用户与角色关系 |             //删除关系表用户与资源关系,用户与权限关系,用户与角色关系 | ||||||
|             await db.Deleteable<SysRelation>(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync().ConfigureAwait(false); |             await db.Deleteable<SysRelation>(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category)).ExecuteCommandAsync().ConfigureAwait(false); | ||||||
| @@ -718,16 +718,16 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService | |||||||
|     public void DeleteUserFromCache(IEnumerable<long> ids) |     public void DeleteUserFromCache(IEnumerable<long> ids) | ||||||
|     { |     { | ||||||
|         var userIds = ids.Select(it => it.ToString()).ToArray();//id转string列表 |         var userIds = ids.Select(it => it.ToString()).ToArray();//id转string列表 | ||||||
|         var sysUsers = App.CacheService.HashGet<SysUser>(CacheConst.Cache_SysUser, userIds).Where(it => it != null);//获取用户列表 |         var sysUsers = App.CacheService.HashGet<SysUser>(CacheConst.Cache_SysUser, userIds);//获取用户列表 | ||||||
|         if (sysUsers.Any() == true) |         if (sysUsers.Count != 0) | ||||||
|         { |         { | ||||||
|             var accounts = sysUsers.Where(it => it != null).Select(it => it.Account).ToArray();//账号集合 |             var accounts = sysUsers.Where(it => it != null).Select(it => it.Account).ToArray();//账号集合 | ||||||
|             var phones = sysUsers.Select(it => it.Phone);//手机号集合 |             var phones = sysUsers.Select(it => it?.Phone);//手机号集合 | ||||||
|  |  | ||||||
|             if (sysUsers.Any(it => it.TenantId != null))//如果有租户id不是空的表示是多租户模式 |             if (sysUsers.Any(it => it?.TenantId != null))//如果有租户id不是空的表示是多租户模式 | ||||||
|             { |             { | ||||||
|                 var userAccountKey = CacheConst.Cache_SysUserAccount; |                 var userAccountKey = CacheConst.Cache_SysUserAccount; | ||||||
|                 var tenantIds = sysUsers.Where(it => it.TenantId != null).Select(it => it.TenantId.Value).Distinct().ToArray();//租户id列表 |                 var tenantIds = sysUsers.Where(it => it?.TenantId != null).Select(it => it.TenantId.Value).Distinct().ToArray();//租户id列表 | ||||||
|                 foreach (var tenantId in tenantIds) |                 foreach (var tenantId in tenantIds) | ||||||
|                 { |                 { | ||||||
|                     userAccountKey = $"{userAccountKey}:{tenantId}"; |                     userAccountKey = $"{userAccountKey}:{tenantId}"; | ||||||
|   | |||||||
| @@ -185,12 +185,12 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi | |||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|  |  | ||||||
|         //更新指定字段 |         //更新指定字段 | ||||||
|         var result = await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser |         var result = (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser | ||||||
|         { |         { | ||||||
|             Email = input.Email, |             Email = input.Email, | ||||||
|             Phone = input.Phone, |             Phone = input.Phone, | ||||||
|             Avatar = input.Avatar, |             Avatar = input.Avatar, | ||||||
|         }, it => it.Id == UserManager.UserId).ConfigureAwait(false); |         }, it => it.Id == UserManager.UserId).ConfigureAwait(false)) > 0; | ||||||
|         if (result) |         if (result) | ||||||
|             _userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据 |             _userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据 | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -119,7 +119,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi | |||||||
|     public void Add(VerificatInfo verificatInfo) |     public void Add(VerificatInfo verificatInfo) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         db.Insertable<VerificatInfo>(verificatInfo).ExecuteCommand(); |         db.InsertableT<VerificatInfo>(verificatInfo).ExecuteCommand(); | ||||||
|         VerificatInfoService.RemoveCache(verificatInfo.Id); |         VerificatInfoService.RemoveCache(verificatInfo.Id); | ||||||
|         if (verificatInfo != null) |         if (verificatInfo != null) | ||||||
|             VerificatInfoService.SetCahce(verificatInfo); |             VerificatInfoService.SetCahce(verificatInfo); | ||||||
| @@ -132,7 +132,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi | |||||||
|     public void Update(VerificatInfo verificatInfo) |     public void Update(VerificatInfo verificatInfo) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         db.Updateable<VerificatInfo>(verificatInfo).ExecuteCommand(); |         db.UpdateableT<VerificatInfo>(verificatInfo).ExecuteCommand(); | ||||||
|         VerificatInfoService.RemoveCache(verificatInfo.Id); |         VerificatInfoService.RemoveCache(verificatInfo.Id); | ||||||
|         if (verificatInfo != null) |         if (verificatInfo != null) | ||||||
|             VerificatInfoService.SetCahce(verificatInfo); |             VerificatInfoService.SetCahce(verificatInfo); | ||||||
| @@ -145,7 +145,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi | |||||||
|     public void Delete(long id) |     public void Delete(long id) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         db.Deleteable<VerificatInfo>(id).ExecuteCommand(); |         db.Deleteable<VerificatInfo>(a => a.Id == id).ExecuteCommand(); | ||||||
|         VerificatInfoService.RemoveCache(id); |         VerificatInfoService.RemoveCache(id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,9 +5,10 @@ | |||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||||
|  | 		 | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| @@ -19,7 +20,7 @@ | |||||||
| 	 | 	 | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> | 		<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" /> | 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" /> | ||||||
| @@ -27,10 +28,10 @@ | |||||||
| 		<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" /> | 		<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" /> | ||||||
|  |  | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | 	<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' "> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(NET10Version)" /> | ||||||
| 		<PackageReference Include="System.Formats.Asn1" Version="$(NET9Version)" /> | 		<PackageReference Include="System.Formats.Asn1" Version="$(NET10Version)" /> | ||||||
| 		<PackageReference Include="System.Threading.RateLimiting" Version="$(NET9Version)" /> | 		<PackageReference Include="System.Threading.RateLimiting" Version="$(NET10Version)" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<Content Remove="SeedData\Admin\*.json" /> | 		<Content Remove="SeedData\Admin\*.json" /> | ||||||
|   | |||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://thingsgateway.cn/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | public static class USheetDataHelpers | ||||||
|  | { | ||||||
|  |     public static USheetDatas GetUSheetDatas(Dictionary<string, object> data) | ||||||
|  |     { | ||||||
|  |         var uSheetDatas = new USheetDatas(); | ||||||
|  |  | ||||||
|  |         foreach (var a in data) | ||||||
|  |         { | ||||||
|  |             var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList(); | ||||||
|  |  | ||||||
|  |             var uSheetData = new USheetData(); | ||||||
|  |             uSheetData.id = a.Key; | ||||||
|  |             uSheetData.name = a.Key; | ||||||
|  |  | ||||||
|  |             for (int row1 = 0; row1 < value.Count; row1++) | ||||||
|  |             { | ||||||
|  |                 if (row1 == 0) | ||||||
|  |                 { | ||||||
|  |                     Dictionary<int, USheetCelldata> usheetColldata = new(); | ||||||
|  |                     int col = 0; | ||||||
|  |                     foreach (var colData in value[row1]) | ||||||
|  |                     { | ||||||
|  |                         usheetColldata.Add(col, new USheetCelldata() { v = colData.Key }); | ||||||
|  |                         col++; | ||||||
|  |                     } | ||||||
|  |                     uSheetData.cellData.Add(row1, usheetColldata); | ||||||
|  |                 } | ||||||
|  |                 { | ||||||
|  |                     Dictionary<int, USheetCelldata> usheetColldata = new(); | ||||||
|  |                     int col = 0; | ||||||
|  |                     foreach (var colData in value[row1]) | ||||||
|  |                     { | ||||||
|  |                         usheetColldata.Add(col, new USheetCelldata() { v = colData.Value }); | ||||||
|  |                         col++; | ||||||
|  |                     } | ||||||
|  |                     uSheetData.cellData.Add(row1 + 1, usheetColldata); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             uSheetData.rowCount = uSheetData.cellData.Count + 100; | ||||||
|  |             uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0; | ||||||
|  |             uSheetDatas.sheets.Add(a.Key, uSheetData); | ||||||
|  |         } | ||||||
|  |         return uSheetDatas; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,9 +4,9 @@ | |||||||
|  |  | ||||||
| <div class="tg-table h-100"> | <div class="tg-table h-100"> | ||||||
|  |  | ||||||
|     <Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate" |     <Table Id=@Id TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate" | ||||||
|            DataService="DataService" CreateItemCallback="CreateItemCallback!" |            DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode OnColumnCreating=OnColumnCreating | ||||||
|            IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" BeforeShowEditDialogCallback="BeforeShowEditDialogCallback!" |            IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!" | ||||||
|            IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender |            IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender | ||||||
|            ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton |            ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton | ||||||
|            ShowEmpty="ShowEmpty" EmptyText="@EmptyText" EmptyImage="@($"{WebsiteConst.DefaultResourceUrl}images/empty.svg")" SortString="@SortString" EditDialogSize="EditDialogSize" |            ShowEmpty="ShowEmpty" EmptyText="@EmptyText" EmptyImage="@($"{WebsiteConst.DefaultResourceUrl}images/empty.svg")" SortString="@SortString" EditDialogSize="EditDialogSize" | ||||||
| @@ -14,7 +14,7 @@ | |||||||
|            ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo |            ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo | ||||||
|            SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText |            SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText | ||||||
|            ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode |            ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode | ||||||
|            ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView |            ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView OnColumnVisibleChanged=OnColumnVisibleChanged | ||||||
|            FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn |            FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn | ||||||
|            IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval |            IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval | ||||||
|            AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh |            AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh | ||||||
| @@ -29,7 +29,7 @@ | |||||||
|            ShowMultiFilterHeader=ShowMultiFilterHeader |            ShowMultiFilterHeader=ShowMultiFilterHeader | ||||||
|            ShowFilterHeader=ShowFilterHeader |            ShowFilterHeader=ShowFilterHeader | ||||||
|            ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth" |            ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth" | ||||||
|            CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!" |            CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!" | ||||||
|            ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!" |            ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!" | ||||||
|            DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!" |            DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!" | ||||||
|            DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!" |            DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!" | ||||||
| @@ -41,6 +41,7 @@ | |||||||
|            DoubleClickToEdit="DoubleClickToEdit" |            DoubleClickToEdit="DoubleClickToEdit" | ||||||
|            OnDoubleClickCellCallback="OnDoubleClickCellCallback" |            OnDoubleClickCellCallback="OnDoubleClickCellCallback" | ||||||
|            OnDoubleClickRowCallback="OnDoubleClickRowCallback" |            OnDoubleClickRowCallback="OnDoubleClickRowCallback" | ||||||
|  |            RowContentTemplate="RowContentTemplate" | ||||||
|            OnClickRowCallback="OnClickRowCallback"> |            OnClickRowCallback="OnClickRowCallback"> | ||||||
|     </Table> |     </Table> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -13,6 +13,41 @@ namespace ThingsGateway.Admin.Razor; | |||||||
| [CascadingTypeParameter(nameof(TItem))] | [CascadingTypeParameter(nameof(TItem))] | ||||||
| public partial class AdminTable<TItem> where TItem : class, new() | public partial class AdminTable<TItem> where TItem : class, new() | ||||||
| { | { | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public Func<string,bool, Task> OnColumnVisibleChanged { get; set; } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.OnColumnCreating"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public Func<List<ITableColumn>,Task> OnColumnCreating { get; set; } | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.RenderMode"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public TableRenderMode RenderMode { get; set; } | ||||||
|  |  | ||||||
|  |     public List<ITableColumn> Columns => Instance?.Columns; | ||||||
|  |  | ||||||
|  |     public IEnumerable<ITableColumn> GetVisibleColumns => Instance?.GetVisibleColumns(); | ||||||
|  |     public List<TItem> Rows => Instance?.Rows; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public EventCallback<List<TItem>> SelectedRowsChanged { get; set; } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.SelectedRows"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public List<TItem> SelectedRows { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     private async Task privateSelectedRowsChanged(List<TItem> items) | ||||||
|  |     { | ||||||
|  |         SelectedRows = items; | ||||||
|  |         if (SelectedRowsChanged.HasDelegate) | ||||||
|  |             await SelectedRowsChanged.InvokeAsync(items); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.DoubleClickToEdit"/> |     /// <inheritdoc cref="Table{TItem}.DoubleClickToEdit"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public bool DoubleClickToEdit { get; set; } = false; |     public bool DoubleClickToEdit { get; set; } = false; | ||||||
| @@ -22,6 +57,10 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
|     /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/> |     /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; } |     public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; } | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.RowContentTemplate"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/> |     /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem, Task>? OnClickRowCallback { get; set; } |     public Func<TItem, Task>? OnClickRowCallback { get; set; } | ||||||
| @@ -128,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
|     [Parameter] |     [Parameter] | ||||||
|     public IDataService<TItem> DataService { get; set; } |     public IDataService<TItem> DataService { get; set; } | ||||||
|  |  | ||||||
|  |     [Parameter] | ||||||
|  |     public string? Id { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/> |     /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem> CreateItemCallback { get; set; } |     public Func<TItem> CreateItemCallback { get; set; } | ||||||
| @@ -210,14 +252,6 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
|     [Parameter] |     [Parameter] | ||||||
|     public RenderFragment<TItem>? SearchTemplate { get; set; } |     public RenderFragment<TItem>? SearchTemplate { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.SelectedRows"/> |  | ||||||
|     [Parameter] |  | ||||||
|     public List<TItem>? SelectedRows { get; set; } = new List<TItem>(); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/> |  | ||||||
|     [Parameter] |  | ||||||
|     public EventCallback<List<TItem>> SelectedRowsChanged { get; set; } |  | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/> |     /// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem, string?>? SetRowClassFormatter { get; set; } |     public Func<TItem, string?>? SetRowClassFormatter { get; set; } | ||||||
| @@ -266,6 +300,15 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
|     [Parameter] |     [Parameter] | ||||||
|     public bool ShowExportButton { get; set; } = false; |     public bool ShowExportButton { get; set; } = false; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.DisableEditButtonCallback"/> | ||||||
|  |     public Func<List<TItem>, bool> DisableEditButtonCallback { get; set; } = (list) => | ||||||
|  |     list.Count != 1; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.DisableDeleteButtonCallback"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public Func<List<TItem>, bool> DisableDeleteButtonCallback { get; set; } = (list) => | ||||||
|  |     list.Count <= 0; | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.ShowExportCsvButton"/> |     /// <inheritdoc cref="Table{TItem}.ShowExportCsvButton"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public bool ShowExportCsvButton { get; set; } = false; |     public bool ShowExportCsvButton { get; set; } = false; | ||||||
|   | |||||||
| @@ -31,12 +31,13 @@ public partial class ChoiceTable<TItem> where TItem : class, new() | |||||||
|  |  | ||||||
|     public async Task OnAddAsync(IEnumerable<TItem> selectorOutputs) |     public async Task OnAddAsync(IEnumerable<TItem> selectorOutputs) | ||||||
|     { |     { | ||||||
|         if (MaxCount > 0 && selectorOutputs.Count() + SelectedRows.Count > MaxCount) |         var data = selectorOutputs is IReadOnlyCollection<TItem> list ? list : selectorOutputs.ToList(); | ||||||
|  |         if (MaxCount > 0 && data.Count + SelectedRows.Count > MaxCount) | ||||||
|         { |         { | ||||||
|             await ToastService.Warning(AdminLocalizer["MaxCount"]); |             await ToastService.Warning(AdminLocalizer["MaxCount"]); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         foreach (var item in selectorOutputs) |         foreach (var item in data) | ||||||
|         { |         { | ||||||
|             SelectedRows.Add(item); |             SelectedRows.Add(item); | ||||||
|             await table2.QueryAsync(); |             await table2.QueryAsync(); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| @namespace ThingsGateway.Gateway.Razor | @namespace ThingsGateway.Admin.Razor | ||||||
| @using ThingsGateway.Admin.Application | @using ThingsGateway.Admin.Application | ||||||
| @using ThingsGateway.Admin.Razor | @using ThingsGateway.Admin.Razor | ||||||
| @using ThingsGateway.Gateway.Application |  | ||||||
| 
 | 
 | ||||||
| <div class="h-600px"> | <div class="h-600px"> | ||||||
|     <UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet> |     <UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet> | ||||||
| @@ -8,9 +8,10 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
|  | using ThingsGateway.Admin.Application; | ||||||
| using ThingsGateway.NewLife.Json.Extension; | using ThingsGateway.NewLife.Json.Extension; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Razor; | namespace ThingsGateway.Admin.Razor; | ||||||
| 
 | 
 | ||||||
| public partial class USheet | public partial class USheet | ||||||
| { | { | ||||||
| @@ -30,7 +30,7 @@ public class BlazorAppContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 全部菜单 |     /// 全部菜单 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public IEnumerable<SysResource> AllMenus { get; private set; } |     public List<SysResource> AllMenus { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当前用户 |     /// 当前用户 | ||||||
| @@ -42,22 +42,22 @@ public class BlazorAppContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 用户个人菜单 |     /// 用户个人菜单 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public IEnumerable<MenuItem> OwnMenuItems { get; private set; } |     public List<MenuItem> OwnMenuItems { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 不同模块的菜单 |     /// 不同模块的菜单 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public IEnumerable<MenuItem> AllOwnMenuItems { get; private set; } |     public List<MenuItem> AllOwnMenuItems { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 用户个人菜单,多个模块 |     /// 用户个人菜单,多个模块 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public IEnumerable<SysResource> OwnMenus { get; private set; } |     public List<SysResource> OwnMenus { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 用户个人菜单,非树形 |     /// 用户个人菜单,非树形 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public IEnumerable<MenuItem> OwnSameLevelMenuItems { get; private set; } |     public List<MenuItem> OwnSameLevelMenuItems { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 个人工作台 |     /// 个人工作台 | ||||||
| @@ -67,9 +67,9 @@ public class BlazorAppContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 用户个人快捷方式菜单 |     /// 用户个人快捷方式菜单 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public IEnumerable<SysResource> UserWorkbenchOutputs { get; private set; } |     public List<SysResource> UserWorkbenchOutputs { get; private set; } | ||||||
|  |  | ||||||
|     public IEnumerable<SysResource> AllResource { get; private set; } |     public List<SysResource> AllResource { get; private set; } | ||||||
|  |  | ||||||
|     private ISysResourceService ResourceService { get; } |     private ISysResourceService ResourceService { get; } | ||||||
|     private ISysUserService SysUserService { get; } |     private ISysUserService SysUserService { get; } | ||||||
| @@ -93,7 +93,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 = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu); |             AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu).ToList(); | ||||||
|  |  | ||||||
|             if (moduleId == null) |             if (moduleId == null) | ||||||
|             { |             { | ||||||
| @@ -123,17 +123,26 @@ public class BlazorAppContext | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId); |             var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId); | ||||||
|             OwnMenuItems = ResourceUtil.BuildMenuTrees(ownMenus).ToList(); |             OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList(); | ||||||
|             AllOwnMenuItems = ResourceUtil.BuildMenuTrees(OwnMenus).ToList(); |             AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList(); | ||||||
|             OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem() |             OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => | ||||||
|             { |             { | ||||||
|                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All, |                 var menu = new MenuItem() | ||||||
|                 Text = item.Title, |                 { | ||||||
|                 Icon = item.Icon, |                     Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix, | ||||||
|                 Url = item.Href, |                     Text = item.Title, | ||||||
|                 Target = item.Target.ToString(), |                     Icon = item.Icon, | ||||||
|             }); |                     Url = item.Href, | ||||||
|             UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)); |                     Target = item.Target.ToString(), | ||||||
|  |                 }; | ||||||
|  |                 if (menu.Url.IsNullOrEmpty()) | ||||||
|  |                 { | ||||||
|  |                     menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix; | ||||||
|  |                 } | ||||||
|  |                 return menu; | ||||||
|  |  | ||||||
|  |             }).ToList(); | ||||||
|  |             UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ public partial class EditPagePolicy | |||||||
|  |  | ||||||
|     protected override Task OnParametersSetAsync() |     protected override Task OnParametersSetAsync() | ||||||
|     { |     { | ||||||
|         ShortcutsTreeViewItems = ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); |         ShortcutsTreeViewItems = AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); | ||||||
|         return base.OnParametersSetAsync(); |         return base.OnParametersSetAsync(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -48,6 +48,6 @@ public partial class EditPagePolicy | |||||||
|     { |     { | ||||||
|         await Task.CompletedTask; |         await Task.CompletedTask; | ||||||
|         ShortcutsSearchText = searchText; |         ShortcutsSearchText = searchText; | ||||||
|         return ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); |         return AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable | |||||||
|             ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)]; |             ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)]; | ||||||
|             ChartDataSource.Options.X.Title = Localizer["DateTime"]; |             ChartDataSource.Options.X.Title = Localizer["DateTime"]; | ||||||
|             ChartDataSource.Options.Y.Title = Localizer["Data"]; |             ChartDataSource.Options.Y.Title = Localizer["Data"]; | ||||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz")); |             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm")); | ||||||
|             ChartDataSource.Data.Add(new ChartDataset() |             ChartDataSource.Data.Add(new ChartDataset() | ||||||
|             { |             { | ||||||
|                 Tension = 0.4f, |                 Tension = 0.4f, | ||||||
| @@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable | |||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos(); |             var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos(); | ||||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz")); |             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm")); | ||||||
|             ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage); |             ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage); | ||||||
|             ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage); |             ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage); | ||||||
|             ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage); |             ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage); | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ public partial class MenuChoiceDialog | |||||||
|         var all = (await SysResourceService.GetAllAsync()); |         var all = (await SysResourceService.GetAllAsync()); | ||||||
|         var items = all.Where(a => a.Category == ResourceCategoryEnum.Menu && a.Module == ModuleId); |         var items = all.Where(a => a.Category == ResourceCategoryEnum.Menu && a.Module == ModuleId); | ||||||
|         ModuleTitle = all.FirstOrDefault(a => a.Id == ModuleId)?.Title; |         ModuleTitle = all.FirstOrDefault(a => a.Id == ModuleId)?.Title; | ||||||
|         Items = ResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem); |         Items = AdminResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem); | ||||||
|         await base.OnParametersSetAsync(); |         await base.OnParametersSetAsync(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,12 +26,12 @@ | |||||||
|                 OnQueryAsync="OnQueryAsync" CustomerSearchModel="@CustomerSearchModel" |                 OnQueryAsync="OnQueryAsync" CustomerSearchModel="@CustomerSearchModel" | ||||||
|                 OnSaveAsync="Save" OnDeleteAsync="Delete"> |                 OnSaveAsync="Save" OnDeleteAsync="Delete"> | ||||||
|         <TableToolbarTemplate> |         <TableToolbarTemplate> | ||||||
|             <PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count<=0||!AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy"> |             <PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count <= 0 || !AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy"> | ||||||
|                 <BodyTemplate> |                 <BodyTemplate> | ||||||
|                     <Select Items="ModuleSelectedItems" @bind-Value=CopyModule ShowLabel="false" /> |                     <Select Items="ModuleSelectedItems" @bind-Value=CopyModule ShowLabel="false" /> | ||||||
|                 </BodyTemplate> |                 </BodyTemplate> | ||||||
|             </PopConfirmButton> |             </PopConfirmButton> | ||||||
|             <PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent"> |             <PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count != 1 || !AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent"> | ||||||
|                 <BodyTemplate> |                 <BodyTemplate> | ||||||
|                     <div class="overflow-y-auto" style="height:500px"> |                     <div class="overflow-y-auto" style="height:500px"> | ||||||
|                         <TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" /> |                         <TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" /> | ||||||
|   | |||||||
| @@ -39,8 +39,8 @@ public partial class SysResourcePage | |||||||
|  |  | ||||||
|     protected override async Task OnParametersSetAsync() |     protected override async Task OnParametersSetAsync() | ||||||
|     { |     { | ||||||
|         ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); |         ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); | ||||||
|         MenuItems = ResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList(); |         MenuItems = AdminResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList(); | ||||||
|  |  | ||||||
|         await base.OnParametersSetAsync(); |         await base.OnParametersSetAsync(); | ||||||
|     } |     } | ||||||
| @@ -49,7 +49,7 @@ public partial class SysResourcePage | |||||||
|  |  | ||||||
|     private async Task<QueryData<SysResource>> OnQueryAsync(QueryPageOptions options) |     private async Task<QueryData<SysResource>> OnQueryAsync(QueryPageOptions options) | ||||||
|     { |     { | ||||||
|         MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(ResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList(); |         MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(AdminResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList(); | ||||||
|  |  | ||||||
|         var data = await SysResourceService.PageAsync(options, CustomerSearchModel); |         var data = await SysResourceService.PageAsync(options, CustomerSearchModel); | ||||||
|         return data; |         return data; | ||||||
| @@ -93,7 +93,7 @@ public partial class SysResourcePage | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             var result = await SysResourceService.DeleteResourceAsync(sysResources.Select(a => a.Id)); |             var result = await SysResourceService.DeleteResourceAsync(sysResources.Select(a => a.Id).ToHashSet()); | ||||||
|             if (ReloadUser != null) |             if (ReloadUser != null) | ||||||
|             { |             { | ||||||
|                 await ReloadUser(); |                 await ReloadUser(); | ||||||
| @@ -136,14 +136,14 @@ public partial class SysResourcePage | |||||||
|     private async Task<IEnumerable<TableTreeNode<SysResource>>> OnTreeExpand(SysResource menu) |     private async Task<IEnumerable<TableTreeNode<SysResource>>> OnTreeExpand(SysResource menu) | ||||||
|     { |     { | ||||||
|         var sysResources = await SysResourceService.GetAllAsync(); |         var sysResources = await SysResourceService.GetAllAsync(); | ||||||
|         var result = ResourceUtil.BuildTableTrees(sysResources, menu.Id); |         var result = AdminResourceUtil.BuildTableTrees(sysResources, menu.Id); | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static async Task<IEnumerable<TableTreeNode<SysResource>>> TreeNodeConverter(IEnumerable<SysResource> items) |     private static async Task<IEnumerable<TableTreeNode<SysResource>>> TreeNodeConverter(IEnumerable<SysResource> items) | ||||||
|     { |     { | ||||||
|         await Task.CompletedTask; |         await Task.CompletedTask; | ||||||
|         var result = ResourceUtil.BuildTableTrees(items, 0); |         var result = AdminResourceUtil.BuildTableTrees(items, 0); | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ public partial class SysRolePage | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             return await SysRoleService.DeleteRoleAsync(sysRoles.Select(a => a.Id)); |             return await SysRoleService.DeleteRoleAsync(sysRoles.Select(a => a.Id).ToHashSet()); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ public partial class GrantResourceDialog | |||||||
|     { |     { | ||||||
|         var items = (await SysResourceService.GetAllAsync()).Where(a => a.Category != ResourceCategoryEnum.Module).OrderBy(a => a.Module).ThenBy(a => a.Id).ToList(); |         var items = (await SysResourceService.GetAllAsync()).Where(a => a.Category != ResourceCategoryEnum.Module).OrderBy(a => a.Module).ThenBy(a => a.Id).ToList(); | ||||||
|  |  | ||||||
|         Items = ResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem); |         Items = AdminResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem); | ||||||
|         ModuleList = (await SysResourceService.GetAllAsync()).Where(a => a.Category == ResourceCategoryEnum.Module).ToList(); |         ModuleList = (await SysResourceService.GetAllAsync()).Where(a => a.Category == ResourceCategoryEnum.Module).ToList(); | ||||||
|         await base.OnInitializedAsync(); |         await base.OnInitializedAsync(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ public partial class SysUserEdit | |||||||
|         BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.Status)); |         BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.Status)); | ||||||
|         var items = await SysPositionService.SelectorAsync(new PositionSelectorInput()); |         var items = await SysPositionService.SelectorAsync(new PositionSelectorInput()); | ||||||
|         Items = PositionUtil.BuildCascaderItemList(items); |         Items = PositionUtil.BuildCascaderItemList(items); | ||||||
|         ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); |         ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); | ||||||
|         await InvokeAsync(StateHasChanged); |         await InvokeAsync(StateHasChanged); | ||||||
|         await base.OnInitializedAsync(); |         await base.OnInitializedAsync(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
|         </Card> |         </Card> | ||||||
|  |  | ||||||
|     </div> |     </div> | ||||||
|     <div class="col-12 col-sm-10 h-100"> |     <div class="col-12 col-sm-10 h-100 ps-2"> | ||||||
|     <AdminTable @ref=table TItem="SysUser" |     <AdminTable @ref=table TItem="SysUser" | ||||||
|         AutoGenerateColumns="true" |         AutoGenerateColumns="true" | ||||||
|         ShowAdvancedSearch=false |         ShowAdvancedSearch=false | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ public partial class SysUserPage | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             return await SysUserService.DeleteUserAsync(sysUsers.Select(a => a.Id)); |             return await SysUserService.DeleteUserAsync(sysUsers.Select(a => a.Id).ToHashSet()); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -20,5 +20,6 @@ public class Startup : AppStartup | |||||||
|         services.AddScoped<IMenuService, MenuService>(); |         services.AddScoped<IMenuService, MenuService>(); | ||||||
|         services.AddScoped<IAuthRazorService, AuthRazorService>(); |         services.AddScoped<IAuthRazorService, AuthRazorService>(); | ||||||
|         services.AddBootstrapBlazorTableExportService(); |         services.AddBootstrapBlazorTableExportService(); | ||||||
|  |         services.AddBootstrapBlazorWinBoxService(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,18 +5,22 @@ | |||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" /> | 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.3" /> | ||||||
|  | 		<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" /> | ||||||
|  | 		<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> | ||||||
|  | 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup Condition="'$(TargetFramework)'=='net8.0'"> | 	<ItemGroup Condition="'$(TargetFramework)'=='net8.0'"> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET8Version)" /> | 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET8Version)" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	<ItemGroup Condition="'$(TargetFramework)'=='net9.0'"> | 	<ItemGroup Condition="'$(TargetFramework)'=='net10.0'"> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(NET10Version)" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net8.0;net9.0</TargetFrameworks> | 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||||
|  | 		 | ||||||
| 		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>--> | 		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>--> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor; | |||||||
| 
 | 
 | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| [ThingsGateway.DependencyInjection.SuppressSniffer] | [ThingsGateway.DependencyInjection.SuppressSniffer] | ||||||
| public static class ResourceUtil | public static class AdminResourceUtil | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造选择项,ID/TITLE |     /// 构造选择项,ID/TITLE | ||||||
| @@ -41,15 +41,22 @@ public static class ResourceUtil | |||||||
|         return items |         return items | ||||||
|         .Where(it => it.ParentId == parentId) |         .Where(it => it.ParentId == parentId) | ||||||
|         .Select((item, index) => |         .Select((item, index) => | ||||||
|             new MenuItem() |         { | ||||||
|  |             var menu = new MenuItem() | ||||||
|             { |             { | ||||||
|                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All, |                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix, | ||||||
|                 Text = item.Title, |                 Text = item.Title, | ||||||
|                 Icon = item.Icon, |                 Icon = item.Icon, | ||||||
|                 Url = item.Href, |                 Url = item.Href, | ||||||
|                 Target = item.Target.ToString(), |                 Target = item.Target.ToString(), | ||||||
|                 Items = BuildMenuTrees(items, item.Id).ToList() |                 Items = BuildMenuTrees(items, item.Id).ToList() | ||||||
|  |             }; | ||||||
|  |             if (menu.Url.IsNullOrEmpty()) | ||||||
|  |             { | ||||||
|  |                 menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix; | ||||||
|             } |             } | ||||||
|  |             return menu; | ||||||
|  |         } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -29,7 +29,7 @@ | |||||||
|   <Target Name="AdminPostPublish" AfterTargets="Publish"> |   <Target Name="AdminPostPublish" AfterTargets="Publish"> | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|       <!-- setting up the variable for convenience --> |       <!-- setting up the variable for convenience --> | ||||||
|       <AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" /> |       <AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ | |||||||
| #推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway | #推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway | ||||||
|  |  | ||||||
| #aspnetcore9.0环境 | #aspnetcore9.0环境 | ||||||
| FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base | #FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base | ||||||
|  | FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base | ||||||
| COPY .  /app | COPY .  /app | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
| #默认web | #默认web | ||||||
| @@ -13,6 +14,8 @@ EXPOSE 5000 | |||||||
|  |  | ||||||
| # 添加时区环境变量,亚洲,上海 | # 添加时区环境变量,亚洲,上海 | ||||||
| ENV TimeZone=Asia/Shanghai | ENV TimeZone=Asia/Shanghai | ||||||
|  | # 转发头 | ||||||
|  | ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true | ||||||
| # 使用软连接,并且将时区配置覆盖/etc/timezone | # 使用软连接,并且将时区配置覆盖/etc/timezone | ||||||
| RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ | |||||||
| #推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64 | #推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64 | ||||||
|  |  | ||||||
| #aspnetcore9.0环境 | #aspnetcore9.0环境 | ||||||
| FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8  AS base | #FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8  AS base | ||||||
|  | FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-arm64v8 AS base | ||||||
| COPY .  /app | COPY .  /app | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
| #默认web | #默认web | ||||||
| @@ -13,6 +14,8 @@ EXPOSE 5000 | |||||||
|  |  | ||||||
| # 添加时区环境变量,亚洲,上海 | # 添加时区环境变量,亚洲,上海 | ||||||
| ENV TimeZone=Asia/Shanghai | ENV TimeZone=Asia/Shanghai | ||||||
|  | # 转发头 | ||||||
|  | ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true | ||||||
| # 使用软连接,并且将时区配置覆盖/etc/timezone | # 使用软连接,并且将时区配置覆盖/etc/timezone | ||||||
| RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,11 +21,13 @@ | |||||||
|     <link rel="apple-touch-icon" href="favicon.png"> |     <link rel="apple-touch-icon" href="favicon.png"> | ||||||
|     <base href="/" /> |     <base href="/" /> | ||||||
|     <title>ThingsGateway</title> |     <title>ThingsGateway</title> | ||||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") /> |     <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") /> | ||||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") /> |     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") /> | ||||||
|     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") /> |     <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") /> | ||||||
|     <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") /> |     <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css") /> | ||||||
|     <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") /> |     <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") /> | ||||||
|  |     <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") /> | ||||||
|  |  | ||||||
|     @* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@ |     @* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@ | ||||||
|     <!-- PWA Manifest --> |     <!-- PWA Manifest --> | ||||||
|     <link rel="manifest" href="./manifest.json" /> |     <link rel="manifest" href="./manifest.json" /> | ||||||
| @@ -38,12 +40,13 @@ | |||||||
|  |  | ||||||
|     <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> |     <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> | ||||||
|  |  | ||||||
|     <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script> |     <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js")></script> | ||||||
|     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js?v={this.GetType().Assembly.GetName().Version}")></script> |     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script> | ||||||
|     <script src="_framework/blazor.web.js"></script> |     <script src="_framework/blazor.web.js"></script> | ||||||
|     <!-- PWA Service Worker --> |     <!-- PWA Service Worker --> | ||||||
|     <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> |     <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> | ||||||
|  |  | ||||||
|  |     <script src="pwa-install.js"></script> | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ | |||||||
|                         <Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" /> |                         <Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" /> | ||||||
|                     } |                     } | ||||||
|                     @* 版本号 *@ |                     @* 版本号 *@ | ||||||
|                     <div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div> |                     <div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div> | ||||||
|  |  | ||||||
|                     @* 主题切换 *@ |                     @* 主题切换 *@ | ||||||
|                     @* <ThemeToggle /> *@ |                     @* <ThemeToggle /> *@ | ||||||
| @@ -89,12 +89,19 @@ | |||||||
|                     </div> |                     </div> | ||||||
|                 </Side> |                 </Side> | ||||||
|                 <Main> |                 <Main> | ||||||
|                         <Tab @ref=_tab ClickTabToNavigation="true" ShowToolbar="true" ShowContextMenu="true" ShowContextMenuFullScreen="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true |                     <Tab @ref=_tab ClickTabToNavigation="true" ShowToolbar="true" ShowContextMenu="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true | ||||||
|                              AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems" |                          ShowFullscreenToolbarButton=false ShowContextMenuFullScreen=false ShowFullScreen=false AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems" | ||||||
|                              DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=> |                          DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=> | ||||||
|                              { |                                                                                   { | ||||||
|                              return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty())); |                                                                                       return Task.FromResult(!(a.Url == "/" || a.Url.IsNullOrEmpty())); | ||||||
|                              })> |                                                                                   }) | ||||||
|  |                                                                                   > | ||||||
|  |                         <BeforeContextMenuTemplate> | ||||||
|  |  | ||||||
|  |                                                      <ContextMenuItem Icon="fa fa-window-restore" Text="@Localizer["WindowRestore"]" OnClick="WinboxRender"></ContextMenuItem> | ||||||
|  |                                                      <ContextMenuDivider></ContextMenuDivider> | ||||||
|  |  | ||||||
|  |                                                  </BeforeContextMenuTemplate> | ||||||
|                         </Tab> |                         </Tab> | ||||||
|                 </Main> |                 </Main> | ||||||
|                 <NotAuthorized> |                 <NotAuthorized> | ||||||
|   | |||||||
| @@ -120,6 +120,38 @@ public partial class MainLayout : IDisposable | |||||||
|  |  | ||||||
|     #endregion 注销 |     #endregion 注销 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private async Task WinboxRender(ContextMenuItem item, object? context) | ||||||
|  |     { | ||||||
|  |         if (context is TabItem tabItem) | ||||||
|  |         { | ||||||
|  |             await WinboxRender(tabItem.ChildContent, tabItem.Text); | ||||||
|  |             //await _tab.RemoveTab(tabItem); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     [Inject] | ||||||
|  |     [NotNull] | ||||||
|  |     private WinBoxService? WinBoxService { get; set; } | ||||||
|  |  | ||||||
|  |     private async Task WinboxRender(RenderFragment item, string title) | ||||||
|  |     { | ||||||
|  |         if (item != null) | ||||||
|  |         { | ||||||
|  |             var option = new WinBoxOption() | ||||||
|  |             { | ||||||
|  |                 Title = title, | ||||||
|  |                 ContentTemplate = item, | ||||||
|  |                 Max = false, | ||||||
|  |                 Width = "80%", | ||||||
|  |                 Height = "80%", | ||||||
|  |                 Top = "0%", | ||||||
|  |                 Left = "10%", | ||||||
|  |                 Background = "var(--bb-primary-color)", | ||||||
|  |                 Overflow = true | ||||||
|  |             }; | ||||||
|  |             await WinBoxService.Show(option); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     private string _versionString = string.Empty; |     private string _versionString = string.Empty; | ||||||
|     [Inject] |     [Inject] | ||||||
|     [NotNull] |     [NotNull] | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ | |||||||
|     </app> |     </app> | ||||||
|      |      | ||||||
|     <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> |     <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> | ||||||
|     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script> |     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script> | ||||||
|     <script src="_framework/blazor.server.js"></script> |     <script src="_framework/blazor.server.js"></script> | ||||||
|  |  | ||||||
|        <!-- PWA Service Worker --> |        <!-- PWA Service Worker --> | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ using System.Text; | |||||||
|  |  | ||||||
| using ThingsGateway.Admin.Application; | using ThingsGateway.Admin.Application; | ||||||
| using ThingsGateway.DB; | using ThingsGateway.DB; | ||||||
|  | using ThingsGateway.NewLife; | ||||||
| using ThingsGateway.NewLife.Log; | using ThingsGateway.NewLife.Log; | ||||||
|  |  | ||||||
| namespace ThingsGateway.AdminServer; | namespace ThingsGateway.AdminServer; | ||||||
| @@ -64,7 +65,7 @@ public class Program | |||||||
|             else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |             else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||||||
|                 builder.Host.UseSystemd(); |                 builder.Host.UseSystemd(); | ||||||
|  |  | ||||||
|             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |             if (Runtime.IsLegacyWindows) | ||||||
|                 builder.Logging.ClearProviders(); //去除默认的事件日志提供者,某些情况下会日志输出异常,导致程序崩溃 |                 builder.Logging.ClearProviders(); //去除默认的事件日志提供者,某些情况下会日志输出异常,导致程序崩溃 | ||||||
|         }).ConfigureBuilder(builder => |         }).ConfigureBuilder(builder => | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -45,11 +45,11 @@ public class Startup : AppStartup | |||||||
|             options.ServicesStopConcurrently = true; |             options.ServicesStopConcurrently = true; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         //// 事件总线 |         // 事件总线 | ||||||
|         //services.AddEventBus(options => |         services.AddEventBus(options => | ||||||
|         //{ |         { | ||||||
|  |  | ||||||
|         //}); |         }); | ||||||
|  |  | ||||||
|         // 任务调度 |         // 任务调度 | ||||||
|         services.AddSchedule(options => options.AddPersistence<JobPersistence>()); |         services.AddSchedule(options => options.AddPersistence<JobPersistence>()); | ||||||
| @@ -132,7 +132,11 @@ public class Startup : AppStartup | |||||||
|         services.Configure<ForwardedHeadersOptions>(options => |         services.Configure<ForwardedHeadersOptions>(options => | ||||||
|         { |         { | ||||||
|             options.ForwardedHeaders = ForwardedHeaders.All; |             options.ForwardedHeaders = ForwardedHeaders.All; | ||||||
|  | #if NET10_0_OR_GREATER | ||||||
|  |             options.KnownIPNetworks.Clear(); | ||||||
|  | #else | ||||||
|             options.KnownNetworks.Clear(); |             options.KnownNetworks.Clear(); | ||||||
|  | #endif | ||||||
|             options.KnownProxies.Clear(); |             options.KnownProxies.Clear(); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -183,25 +187,37 @@ public class Startup : AppStartup | |||||||
|         services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>(); |         services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>(); | ||||||
|         services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>(); |         services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>(); | ||||||
|  |  | ||||||
|  |         if (!NewLife.Runtime.IsLegacyWindows) | ||||||
|  |         { | ||||||
| #if NET9_0_OR_GREATER | #if NET9_0_OR_GREATER | ||||||
|         var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet); |             var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet); | ||||||
| #else | #else | ||||||
|         var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet); |             var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet); | ||||||
| #endif | #endif | ||||||
|         services.AddDataProtection() |             services.AddDataProtection() | ||||||
|             .PersistKeysToFileSystem(new DirectoryInfo("Keys")) |                 .PersistKeysToFileSystem(new DirectoryInfo("Keys")) | ||||||
|             .ProtectKeysWithCertificate(certificate) |                 .ProtectKeysWithCertificate(certificate) | ||||||
|             .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration |                 .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration | ||||||
|             { |                 { | ||||||
|                 EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, |                     EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, | ||||||
|                 ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 |                     ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 | ||||||
|             }); |                 }); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env) |     public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env) | ||||||
|     { |     { | ||||||
|         var app = (WebApplication)applicationBuilder; |         var app = (WebApplication)applicationBuilder; | ||||||
|         app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All, KnownNetworks = { }, KnownProxies = { } }); |         app.UseForwardedHeaders(new ForwardedHeadersOptions | ||||||
|  |         { | ||||||
|  |             ForwardedHeaders = ForwardedHeaders.All, | ||||||
|  | #if NET10_0_OR_GREATER | ||||||
|  |             KnownIPNetworks = { }, | ||||||
|  | #else | ||||||
|  |             KnownNetworks = { }, | ||||||
|  | #endif | ||||||
|  |             KnownProxies = { } | ||||||
|  |         }); | ||||||
|         app.UseBootstrapBlazor(); |         app.UseBootstrapBlazor(); | ||||||
|  |  | ||||||
|         // 启用本地化 |         // 启用本地化 | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||||
|  | 		 | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
| 	<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />--> | 	<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />--> | ||||||
| @@ -52,9 +53,9 @@ | |||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" /> | 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" /> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" /> | 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | 	<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' "> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="$(NET10Version)" /> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(NET10Version)" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	 | 	 | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | let installPromptTriggered = false; | ||||||
|  |  | ||||||
|  | function getCookie(name) { | ||||||
|  |     const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); | ||||||
|  |     return match ? match[2] : null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function hasShownInstallPrompt() { | ||||||
|  |     return getCookie("tgPWAInstallPromptShown") === "true"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function markInstallPromptShown() { | ||||||
|  |     document.cookie = "tgPWAInstallPromptShown=true; max-age=31536000; path=/"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | window.addEventListener('beforeinstallprompt', (e) => { | ||||||
|  |     e.preventDefault(); | ||||||
|  |  | ||||||
|  |     if (!hasShownInstallPrompt() && !installPromptTriggered) { | ||||||
|  |         installPromptTriggered = true; | ||||||
|  |         setTimeout(() => { | ||||||
|  |             e.prompt() | ||||||
|  |                 .then(() => e.userChoice) | ||||||
|  |                 .then(choiceResult => { | ||||||
|  |                     markInstallPromptShown(); | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     // 可选错误处理 | ||||||
|  |                 }); | ||||||
|  |         }, 2000); // 延迟 2 秒提示 | ||||||
|  |     } else { | ||||||
|  |         // console.log("已提示过安装,不再弹出"); | ||||||
|  |     } | ||||||
|  | }); | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
| @@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T> | |||||||
|     { |     { | ||||||
|         lock (((ICollection)m_list).SyncRoot) |         lock (((ICollection)m_list).SyncRoot) | ||||||
|         { |         { | ||||||
|             return m_list.IndexOf(item); |             return m_list.LastIndexOf(item); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,9 +30,27 @@ public class ImportPreviewOutputBase | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 返回状态 |     /// 返回状态 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public ConcurrentList<(int Row, bool Success, string? ErrorMessage)> Results { get; set; } = new(); |     public ConcurrentList<ImportPreviewResult> Results { get; set; } = new(); | ||||||
| } | } | ||||||
|  | public class ImportPreviewResult | ||||||
|  | { | ||||||
|  |     public ImportPreviewResult() | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     public ImportPreviewResult(int row, bool success, string error) | ||||||
|  |     { | ||||||
|  |         this.Row = row; | ||||||
|  |         this.Success = success; | ||||||
|  |         this.ErrorMessage = error; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public int Row { get; set; } | ||||||
|  |  | ||||||
|  |     public bool Success { get; set; } | ||||||
|  |  | ||||||
|  |     public string? ErrorMessage { get; set; } | ||||||
|  | } | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 导入预览 | /// 导入预览 | ||||||
| /// </summary> | /// </summary> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Common; | ||||||
| 
 | 
 | ||||||
| public class SmartTriggerScheduler | public class SmartTriggerScheduler | ||||||
| { | { | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Common; | ||||||
| 
 | 
 | ||||||
| public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string> | public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string> | ||||||
| { | { | ||||||
| @@ -31,8 +31,8 @@ public static class GenericExtensions | |||||||
|  |  | ||||||
|         // 比较oldModel和model的属性,找出差异 |         // 比较oldModel和model的属性,找出差异 | ||||||
|         var differences = properties |         var differences = properties | ||||||
|             .Where(prop => prop.CanRead && prop.CanWrite) // 确保属性可读可写 |             .Where(prop => prop.CanRead && prop.CanWrite && !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 确保属性可读可写 | ||||||
|             .Where(prop => !Equals(prop.GetValue(oldModel), prop.GetValue(model))) // 找出值不同的属性 |                                                                                                                     // 找出值不同的属性 | ||||||
|             .ToDictionary(prop => prop.Name, prop => prop.GetValue(model)); // 将属性名和新值存储到字典中 |             .ToDictionary(prop => prop.Name, prop => prop.GetValue(model)); // 将属性名和新值存储到字典中 | ||||||
|  |  | ||||||
|         // 应用差异到channels列表中的每个Channel对象 |         // 应用差异到channels列表中的每个Channel对象 | ||||||
|   | |||||||
| @@ -16,6 +16,8 @@ using System.Runtime.CompilerServices; | |||||||
| using System.Text.Json; | using System.Text.Json; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extension; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Common.Extension; | namespace ThingsGateway.Common.Extension; | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 对象拓展类 | /// 对象拓展类 | ||||||
| @@ -48,113 +50,7 @@ public static class ObjectExtensions | |||||||
|         bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); |         bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTimeOffset 转换成本地 DateTime |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTime ConvertToDateTime(this DateTimeOffset dateTime) |  | ||||||
|     { |  | ||||||
|         if (dateTime.Offset.Equals(TimeSpan.Zero)) |  | ||||||
|             return dateTime.UtcDateTime; |  | ||||||
|         if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime))) |  | ||||||
|             return dateTime.ToLocalTime().DateTime; |  | ||||||
|         else |  | ||||||
|             return dateTime.DateTime; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTimeOffset? 转换成本地 DateTime? |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime) |  | ||||||
|     { |  | ||||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTime 转换成 DateTimeOffset |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime) |  | ||||||
|     { |  | ||||||
|         return DateTime.SpecifyKind(dateTime, DateTimeKind.Local); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTime? 转换成 DateTimeOffset? |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime) |  | ||||||
|     { |  | ||||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将流保存到本地磁盘 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="stream"></param> |  | ||||||
|     /// <param name="path"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static void CopyToSave(this Stream stream, string path) |  | ||||||
|     { |  | ||||||
|         // 空检查 |  | ||||||
|         if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); |  | ||||||
|  |  | ||||||
|         using var fileStream = File.Create(path); |  | ||||||
|         stream.CopyTo(fileStream); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将字节数组保存到本地磁盘 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="bytes"></param> |  | ||||||
|     /// <param name="path"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static void CopyToSave(this byte[] bytes, string path) |  | ||||||
|     { |  | ||||||
|         using var stream = new MemoryStream(bytes); |  | ||||||
|         stream.CopyToSave(path); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将流保存到本地磁盘 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="stream"></param> |  | ||||||
|     /// <param name="path">需包含文件名完整路径</param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static async Task CopyToSaveAsync(this Stream stream, string path) |  | ||||||
|     { |  | ||||||
|         // 空检查 |  | ||||||
|         if (string.IsNullOrWhiteSpace(path)) |  | ||||||
|         { |  | ||||||
|             throw new ArgumentNullException(nameof(path)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 文件名判断 |  | ||||||
|         if (string.IsNullOrWhiteSpace(Path.GetFileName(path))) |  | ||||||
|         { |  | ||||||
|             throw new ArgumentException("The parameter of <path> parameter must include the complete file name."); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         using var fileStream = File.Create(path); |  | ||||||
|         await stream.CopyToAsync(fileStream).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将字节数组保存到本地磁盘 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="bytes"></param> |  | ||||||
|     /// <param name="path"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static async Task CopyToSaveAsync(this byte[] bytes, string path) |  | ||||||
|     { |  | ||||||
|         using var stream = new MemoryStream(bytes); |  | ||||||
|         await stream.CopyToSaveAsync(path).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 合并两个字典 |     /// 合并两个字典 | ||||||
| @@ -186,7 +82,7 @@ public static class ObjectExtensions | |||||||
|     /// <typeparam name="T"></typeparam> |     /// <typeparam name="T"></typeparam> | ||||||
|     /// <param name="dic">字典</param> |     /// <param name="dic">字典</param> | ||||||
|     /// <param name="newDic">新字典</param> |     /// <param name="newDic">新字典</param> | ||||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) |     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||||
|     { |     { | ||||||
|         foreach (var (key, value) in newDic) |         foreach (var (key, value) in newDic) | ||||||
|         { |         { | ||||||
| @@ -443,10 +339,7 @@ public static class ObjectExtensions | |||||||
|         where TAttribute : Attribute |         where TAttribute : Attribute | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         if (type == null) |         ArgumentNullException.ThrowIfNull(type); | ||||||
|         { |  | ||||||
|             throw new ArgumentNullException(nameof(type)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 检查特性并获取特性对象 |         // 检查特性并获取特性对象 | ||||||
|         return type.IsDefined(typeof(TAttribute), inherit) |         return type.IsDefined(typeof(TAttribute), inherit) | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ public static class ParallelExtensions | |||||||
|     /// <typeparam name="T">集合元素类型</typeparam> |     /// <typeparam name="T">集合元素类型</typeparam> | ||||||
|     /// <param name="source">要操作的集合</param> |     /// <param name="source">要操作的集合</param> | ||||||
|     /// <param name="body">要执行的操作</param> |     /// <param name="body">要执行的操作</param> | ||||||
|     public static void ParallelForEach<T>(this IList<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; |         options.MaxDegreeOfParallelism = Environment.ProcessorCount; | ||||||
| @@ -38,7 +38,7 @@ public static class ParallelExtensions | |||||||
|     /// <typeparam name="T">集合元素类型</typeparam> |     /// <typeparam name="T">集合元素类型</typeparam> | ||||||
|     /// <param name="source">要操作的集合</param> |     /// <param name="source">要操作的集合</param> | ||||||
|     /// <param name="body">要执行的操作</param> |     /// <param name="body">要执行的操作</param> | ||||||
|     public static void ParallelForEach<T>(this IList<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; |         options.MaxDegreeOfParallelism = Environment.ProcessorCount; | ||||||
| @@ -53,7 +53,7 @@ public static class ParallelExtensions | |||||||
|     /// <param name="source">要操作的集合</param> |     /// <param name="source">要操作的集合</param> | ||||||
|     /// <param name="body">要执行的操作</param> |     /// <param name="body">要执行的操作</param> | ||||||
|     /// <param name="parallelCount">最大并行度</param> |     /// <param name="parallelCount">最大并行度</param> | ||||||
|     public static void ParallelForEach<T>(this IList<T> source, Action<T> body, int parallelCount) |     public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body, int parallelCount) | ||||||
|     { |     { | ||||||
|         // 创建并行操作的选项对象,设置最大并行度为指定的值 |         // 创建并行操作的选项对象,设置最大并行度为指定的值 | ||||||
|         var options = new ParallelOptions(); |         var options = new ParallelOptions(); | ||||||
| @@ -109,7 +109,7 @@ public static class ParallelExtensions | |||||||
|     /// <param name="parallelCount">最大并行度</param> |     /// <param name="parallelCount">最大并行度</param> | ||||||
|     /// <param name="cancellationToken">取消操作的标志</param> |     /// <param name="cancellationToken">取消操作的标志</param> | ||||||
|     /// <returns>表示异步操作的任务</returns> |     /// <returns>表示异步操作的任务</returns> | ||||||
|     public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default) |     public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         // 创建并行操作的选项对象,设置最大并行度和取消标志 |         // 创建并行操作的选项对象,设置最大并行度和取消标志 | ||||||
|         var options = new ParallelOptions(); |         var options = new ParallelOptions(); | ||||||
| @@ -126,7 +126,7 @@ public static class ParallelExtensions | |||||||
|     /// <param name="body">异步执行的操作</param> |     /// <param name="body">异步执行的操作</param> | ||||||
|     /// <param name="cancellationToken">取消操作的标志</param> |     /// <param name="cancellationToken">取消操作的标志</param> | ||||||
|     /// <returns>表示异步操作的任务</returns> |     /// <returns>表示异步操作的任务</returns> | ||||||
|     public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default) |     public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken); |         return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | |||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 catch (Exception ex) | ||||||
|                 { |                 { | ||||||
|                     Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); |                     if (Logger?.IsEnabled(LogLevel.Error) == true) | ||||||
|  |                         Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||||
|                 } |                 } | ||||||
|                 return ret; |                 return ret; | ||||||
|             } |             } | ||||||
| @@ -176,7 +177,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | |||||||
|         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); |         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); | ||||||
|         if (jsonLocalizationOptions.IgnoreLocalizerMissing == false) |         if (jsonLocalizationOptions.IgnoreLocalizerMissing == false) | ||||||
|         { |         { | ||||||
|             Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); |             if (Logger?.IsEnabled(LogLevel.Information) == true) | ||||||
|  |                 Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||||
|         } |         } | ||||||
|         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); |         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -27,7 +27,11 @@ public class WebsiteOptions : IConfigurableOptions | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public bool Demo { get; set; } |     public bool Demo { get; set; } | ||||||
|  |  | ||||||
|     public bool WebPageEnable { get; set; } = true; |  | ||||||
|  |     public int MaxBlazorConnections { get; set; } = 5; | ||||||
|  |     public bool BlazorConnectionLimitEnable { get; set; } = false; | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否显示关于页面 |     /// 是否显示关于页面 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -5,15 +5,16 @@ | |||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||||
|  | 		 | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 	 | 	 | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" /> | 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" /> | ||||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.8.1" /> | 		<PackageReference Include="BootstrapBlazor" Version="9.11.2" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
| @@ -23,13 +23,19 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity | |||||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] |     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||||
|     [IgnoreExcel] |     [IgnoreExcel] | ||||||
|     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] |     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public virtual long Id { get; set; } |     public virtual long Id { get; set; } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | public interface IPrimaryKeyEntity | ||||||
|  | { | ||||||
|  |     string ExtJson { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 主键实体基类 | /// 主键实体基类 | ||||||
| /// </summary> | /// </summary> | ||||||
| public abstract class PrimaryKeyEntity : PrimaryIdEntity | public abstract class PrimaryKeyEntity : PrimaryIdEntity, IPrimaryKeyEntity | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 拓展信息 |     /// 拓展信息 | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| // ------------------------------------------------------------------------------ | // ------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using Microsoft.AspNetCore.Components.Forms; | using Microsoft.AspNetCore.Components.Forms; | ||||||
|  | using Microsoft.AspNetCore.Http; | ||||||
|  |  | ||||||
| namespace ThingsGateway.DB; | namespace ThingsGateway.DB; | ||||||
|  |  | ||||||
| @@ -41,4 +42,31 @@ public static class FileExtensions | |||||||
|         } |         } | ||||||
|         return fileName; |         return fileName; | ||||||
|     } |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 存储本地文件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="pPath">存储的第一层目录</param> | ||||||
|  |     /// <param name="file"></param> | ||||||
|  |     /// <returns>文件全路径</returns> | ||||||
|  |     public static async Task<string> StorageLocal(this IFormFile file, string pPath = "imports") | ||||||
|  |     { | ||||||
|  |         string uploadFileFolder = App.WebHostEnvironment?.WebRootPath ?? "wwwroot"!;//赋值路径 | ||||||
|  |         var now = CommonUtils.GetSingleId(); | ||||||
|  |         var filePath = Path.Combine(uploadFileFolder, pPath); | ||||||
|  |         if (!Directory.Exists(filePath))//如果不存在就创建文件夹 | ||||||
|  |             Directory.CreateDirectory(filePath); | ||||||
|  |         //var fileSuffix = Path.GetExtension(file.Name).ToLower();// 文件后缀 | ||||||
|  |         var fileObjectName = $"{now}{file.Name}";//存储后的文件名 | ||||||
|  |         var fileName = Path.Combine(filePath, fileObjectName);//获取文件全路径 | ||||||
|  |         fileName = fileName.Replace("\\", "/");//格式化一系 | ||||||
|  |         //存储文件 | ||||||
|  |         using (var stream = File.Create(Path.Combine(filePath, fileObjectName))) | ||||||
|  |         { | ||||||
|  |             await file.CopyToAsync(stream).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         return fileName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -53,6 +53,8 @@ public static class QueryPageOptionsExtensions | |||||||
|         return datas; |         return datas; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null) |     public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null) | ||||||
|     { |     { | ||||||
|         if (queryFunc != null) |         if (queryFunc != null) | ||||||
| @@ -123,7 +125,36 @@ public static class QueryPageOptionsExtensions | |||||||
|         }; |         }; | ||||||
|         var items = datas.GetData(option, out var totalCount, where); |         var items = datas.GetData(option, out var totalCount, where); | ||||||
|         ret.TotalCount = totalCount; |         ret.TotalCount = totalCount; | ||||||
|  |  | ||||||
|  |         if (totalCount > 0) | ||||||
|  |         { | ||||||
|  |             if (!items.Any() && option.PageIndex != 1) | ||||||
|  |             { | ||||||
|  |                 option.PageIndex = 1; | ||||||
|  |                 items = datas.GetData(option, out totalCount, where); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         ret.Items = items.ToList(); |         ret.Items = items.ToList(); | ||||||
|         return ret; |         return ret; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 根据查询条件返回QueryData | ||||||
|  |     /// </summary> | ||||||
|  |     public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null) | ||||||
|  |     { | ||||||
|  |         var ret = new QueryData<SelectedItem>() | ||||||
|  |         { | ||||||
|  |             IsSorted = false, | ||||||
|  |             IsFiltered = false, | ||||||
|  |             IsAdvanceSearch = false, | ||||||
|  |             IsSearch = !option.SearchText.IsNullOrWhiteSpace() | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         var items = datas.Skip((option.StartIndex)).Take(option.Count); | ||||||
|  |         ret.TotalCount = datas.Count(); | ||||||
|  |  | ||||||
|  |         ret.Items = func(items).ToList(); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -209,16 +209,10 @@ public static class SqlSugarExtensions | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new() |     public static Task<int> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new() | ||||||
|     { |     { | ||||||
|         return await db.Updateable(updateObjs).ExecuteCommandAsync().ConfigureAwait(false) > 0; |         return db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression) | ||||||
|     } |             .ExecuteCommandAsync(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new() |  | ||||||
|     { |  | ||||||
|         return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression) |  | ||||||
|             .ExecuteCommandAsync().ConfigureAwait(false) > 0; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput) |     private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput) | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/en-US.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/en-US.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |   | ||||||
|  |   "ThingsGateway.Admin.Application.BaseDataEntity": { | ||||||
|  |     "CreateOrgId": "CreateOrgId" | ||||||
|  |   }, | ||||||
|  |   "ThingsGateway.Admin.Application.BaseEntity": { | ||||||
|  |     "CreateTime": "CreateTime", | ||||||
|  |     "CreateUser": "CreateUser", | ||||||
|  |     "SortCode": "SortCode", | ||||||
|  |     "UpdateTime": "UpdateTime", | ||||||
|  |     "UpdateUser": "UpdateUser" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |    | ||||||
|  |   "ThingsGateway.DB.BaseDataEntity": { | ||||||
|  |     "CreateOrgId": "创建机构Id" | ||||||
|  |   }, | ||||||
|  |   "ThingsGateway.DB.BaseEntity": { | ||||||
|  |     "CreateTime": "创建时间", | ||||||
|  |     "CreateUser": "创建人", | ||||||
|  |     "SortCode": "排序", | ||||||
|  |     "UpdateTime": "更新时间", | ||||||
|  |     "UpdateUser": "更新人" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -43,12 +43,12 @@ public class SugarAopService : ISugarAopService | |||||||
|                 } |                 } | ||||||
|                 if (sql.StartsWith("INSERT")) |                 if (sql.StartsWith("INSERT")) | ||||||
|                 { |                 { | ||||||
|                     Console.ForegroundColor = ConsoleColor.Yellow; |                     Console.ForegroundColor = ConsoleColor.Blue; | ||||||
|                     DbContext.WriteLog($"添加{config.ConfigId}库操作"); |                     DbContext.WriteLog($"添加{config.ConfigId}库操作"); | ||||||
|                 } |                 } | ||||||
|                 if (sql.StartsWith("DELETE")) |                 if (sql.StartsWith("DELETE")) | ||||||
|                 { |                 { | ||||||
|                     Console.ForegroundColor = ConsoleColor.Red; |                     Console.ForegroundColor = ConsoleColor.Blue; | ||||||
|                     DbContext.WriteLog($"删除{config.ConfigId}库操作"); |                     DbContext.WriteLog($"删除{config.ConfigId}库操作"); | ||||||
|                 } |                 } | ||||||
|                 DbContext.WriteLogWithSql(UtilMethods.GetNativeSql(sql, pars)); |                 DbContext.WriteLogWithSql(UtilMethods.GetNativeSql(sql, pars)); | ||||||
| @@ -62,7 +62,7 @@ public class SugarAopService : ISugarAopService | |||||||
|             if (ex.Parameters == null) return; |             if (ex.Parameters == null) return; | ||||||
|             Console.ForegroundColor = ConsoleColor.Red; |             Console.ForegroundColor = ConsoleColor.Red; | ||||||
|             DbContext.WriteLog($"{config.ConfigId}库操作异常"); |             DbContext.WriteLog($"{config.ConfigId}库操作异常"); | ||||||
|             DbContext.WriteErrorLogWithSql(UtilMethods.GetNativeSql(ex.Sql, (IReadOnlyList<SugarParameter>)ex.Parameters)); |             DbContext.WriteErrorLogWithSql(UtilMethods.GetNativeSql(ex.Sql, ex.Parameters)); | ||||||
|             NewLife.Log.XTrace.WriteException(ex); |             NewLife.Log.XTrace.WriteException(ex); | ||||||
|             Console.ForegroundColor = ConsoleColor.White; |             Console.ForegroundColor = ConsoleColor.White; | ||||||
|         }; |         }; | ||||||
| @@ -110,10 +110,6 @@ public class SugarAopService : ISugarAopService | |||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         //查询数据转换 |  | ||||||
|         db.Aop.DataExecuted = (value, entity) => |  | ||||||
|         { |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         db.Aop.OnLogExecuted = (sql, pars) => |         db.Aop.OnLogExecuted = (sql, pars) => | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new( | |||||||
|     public async Task<bool> DeleteAsync(IEnumerable<T> models) |     public async Task<bool> DeleteAsync(IEnumerable<T> models) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false); |         return await db.Deleteable<T>(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
| @@ -140,18 +140,22 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new( | |||||||
|             return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; |             return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType) |     public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType) | ||||||
|  |     { | ||||||
|  |         return (await SaveReturnCountAsync(model, changedType).ConfigureAwait(false)) > 0; | ||||||
|  |     } | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<int> SaveReturnCountAsync(List<T> model, ItemChangedType changedType) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         if (changedType == ItemChangedType.Add) |         if (changedType == ItemChangedType.Add) | ||||||
|         { |         { | ||||||
|             return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; |             return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; |             return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -42,7 +42,6 @@ public static class CodeFirstUtils | |||||||
|         var seedDataTypes = App.EffectiveTypes |         var seedDataTypes = App.EffectiveTypes | ||||||
|     .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass |     .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass | ||||||
|     && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))) && u.Assembly.FullName == assemblyName); |     && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))) && u.Assembly.FullName == assemblyName); | ||||||
|         if (!seedDataTypes.Any()) return; |  | ||||||
|         foreach (var seedType in seedDataTypes)//遍历种子类 |         foreach (var seedType in seedDataTypes)//遍历种子类 | ||||||
|         { |         { | ||||||
|             //使用与指定参数匹配程度最高的构造函数来创建指定类型的实例。 |             //使用与指定参数匹配程度最高的构造函数来创建指定类型的实例。 | ||||||
| @@ -62,10 +61,12 @@ public static class CodeFirstUtils | |||||||
|             // seedDataTable.TableName = db.EntityMaintenance.GetEntityInfo(entityType).DbTableName;//获取表名 |             // seedDataTable.TableName = db.EntityMaintenance.GetEntityInfo(entityType).DbTableName;//获取表名 | ||||||
|             var ignoreAdd = seedDataMethod!.GetCustomAttribute<IgnoreSeedDataAddAttribute>();//读取忽略插入特性 |             var ignoreAdd = seedDataMethod!.GetCustomAttribute<IgnoreSeedDataAddAttribute>();//读取忽略插入特性 | ||||||
|             var ignoreUpdate = seedDataMethod!.GetCustomAttribute<IgnoreSeedDataUpdateAttribute>();//读取忽略更新特性 |             var ignoreUpdate = seedDataMethod!.GetCustomAttribute<IgnoreSeedDataUpdateAttribute>();//读取忽略更新特性 | ||||||
|  |  | ||||||
|  |             var seedDataList = seedData.ToList(); | ||||||
|             if (entityInfo.Columns.Any(u => u.IsPrimarykey))//判断种子数据是否有主键 |             if (entityInfo.Columns.Any(u => u.IsPrimarykey))//判断种子数据是否有主键 | ||||||
|             { |             { | ||||||
|                 // 按主键进行批量增加和更新 |                 // 按主键进行批量增加和更新 | ||||||
|                 var storage = db.StorageableByObject(seedData.ToList()).ToStorage(); |                 var storage = db.StorageableByObject(seedDataList).ToStorage(); | ||||||
|                 if (ignoreAdd == null) |                 if (ignoreAdd == null) | ||||||
|                     storage.AsInsertable.ExecuteCommand();//执行插入 |                     storage.AsInsertable.ExecuteCommand();//执行插入 | ||||||
|                 if (ignoreUpdate == null && config.IsUpdateSeedData) storage.AsUpdateable.ExecuteCommand();//只有没有忽略更新的特性才执行更新 |                 if (ignoreUpdate == null && config.IsUpdateSeedData) storage.AsUpdateable.ExecuteCommand();//只有没有忽略更新的特性才执行更新 | ||||||
| @@ -75,7 +76,7 @@ public static class CodeFirstUtils | |||||||
|                 //全量插入 |                 //全量插入 | ||||||
|                 // 无主键则只进行插入 |                 // 无主键则只进行插入 | ||||||
|                 if (!db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any() && ignoreAdd == null) |                 if (!db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any() && ignoreAdd == null) | ||||||
|                     db.InsertableByObject(seedData.ToList()).ExecuteCommand(); |                     db.InsertableByObject(seedDataList).ExecuteCommand(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -89,7 +90,6 @@ public static class CodeFirstUtils | |||||||
|         // 获取所有实体表-初始化表结构 |         // 获取所有实体表-初始化表结构 | ||||||
|         var entityTypes = App.EffectiveTypes.Where(u => |         var entityTypes = App.EffectiveTypes.Where(u => | ||||||
|             !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && u.Assembly.FullName == assemblyName); |             !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && u.Assembly.FullName == assemblyName); | ||||||
|         if (!entityTypes.Any()) return;//没有就退出 |  | ||||||
|         foreach (var entityType in entityTypes) |         foreach (var entityType in entityTypes) | ||||||
|         { |         { | ||||||
|             var tenantAtt = entityType.GetCustomAttribute<TenantAttribute>();//获取Sqlsugar多库特性 |             var tenantAtt = entityType.GetCustomAttribute<TenantAttribute>();//获取Sqlsugar多库特性 | ||||||
|   | |||||||
| @@ -136,7 +136,7 @@ public static class DbContext | |||||||
|                 await db.Fastest<TITEM>().PageSize(size).BulkCopyAsync(datas).ConfigureAwait(false); |                 await db.Fastest<TITEM>().PageSize(size).BulkCopyAsync(datas).ConfigureAwait(false); | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 await db.Insertable(datas is IReadOnlyList<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false); |                 await db.Insertable(datas is IReadOnlyCollection<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -155,7 +155,7 @@ public static class DbContext | |||||||
|                 await db.Fastest<TITEM>().PageSize(size).BulkUpdateAsync(datas).ConfigureAwait(false); |                 await db.Fastest<TITEM>().PageSize(size).BulkUpdateAsync(datas).ConfigureAwait(false); | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 await db.Updateable(datas is IReadOnlyList<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false); |                 await db.Updateable(datas is IReadOnlyCollection<TITEM> values ? values : datas.ToList()).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -5,9 +5,10 @@ | |||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||||
|  | 		 | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -18,6 +19,12 @@ | |||||||
| 		<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" /> | 		<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	<ItemGroup> | ||||||
|  | 	  <EmbeddedResource Include="Locales\en-US.json" /> | ||||||
|  | 	  <EmbeddedResource Include="Locales\zh-CN.json" /> | ||||||
|  | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<!--<PackageReference Include="ThingsGateway.Razor" Version="$(SourceGeneratorVersion)" />--> | 		<!--<PackageReference Include="ThingsGateway.Razor" Version="$(SourceGeneratorVersion)" />--> | ||||||
| 		<!--<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />--> | 		<!--<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />--> | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| https://gitee.com/dotnetchina/Furion/commit/8bf85f6908c1630268e45eeec607267a03947d2b | https://gitee.com/dotnetchina/Furion/commit/f1c07d65cccb623aca9d1905bf2e1ac6e4f4b714 | ||||||
| @@ -27,18 +27,27 @@ using System.Security.Claims; | |||||||
| using ThingsGateway.ConfigurableOptions; | using ThingsGateway.ConfigurableOptions; | ||||||
| using ThingsGateway.NewLife.Caching; | using ThingsGateway.NewLife.Caching; | ||||||
| using ThingsGateway.NewLife.Collections; | using ThingsGateway.NewLife.Collections; | ||||||
|  | using ThingsGateway.NewLife.Extension; | ||||||
| using ThingsGateway.NewLife.Log; | using ThingsGateway.NewLife.Log; | ||||||
| using ThingsGateway.Reflection; | using ThingsGateway.Reflection; | ||||||
| using ThingsGateway.Templates; | using ThingsGateway.Templates; | ||||||
|  |  | ||||||
| namespace ThingsGateway; | namespace ThingsGateway; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | public static class WebEnableVariable | ||||||
|  | { | ||||||
|  |     public static bool WebEnable => Environment.GetEnvironmentVariable(nameof(WebEnable)).ToBoolean(true); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 全局应用类 | /// 全局应用类 | ||||||
| /// </summary> | /// </summary> | ||||||
| [SuppressSniffer] | [SuppressSniffer] | ||||||
| public static class App | public static class App | ||||||
| { | { | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 私有设置,避免重复解析 |     /// 私有设置,避免重复解析 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -157,7 +166,7 @@ public static class App | |||||||
|     var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>(); |     var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>(); | ||||||
|     try |     try | ||||||
|     { |     { | ||||||
|         return httpContextAccessor.HttpContext; |         return httpContextAccessor?.HttpContext; | ||||||
|     } |     } | ||||||
|     catch |     catch | ||||||
|     { |     { | ||||||
| @@ -545,10 +554,9 @@ public static class App | |||||||
|         { |         { | ||||||
|             types = ass.GetTypes(); |             types = ass.GetTypes(); | ||||||
|         } |         } | ||||||
|         catch |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             XTrace.Log.Warn($"Error load `{ass.FullName}` assembly."); |             XTrace.Log.Warn($"Error load `{ass.FullName}` assembly. : {ex.Message}"); | ||||||
|             Console.WriteLine($"Error load `{ass.FullName}` assembly."); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return types.Where(u => u.IsPublic && !u.IsDefined(typeof(SuppressSnifferAttribute), false)); |         return types.Where(u => u.IsPublic && !u.IsDefined(typeof(SuppressSnifferAttribute), false)); | ||||||
|   | |||||||
| @@ -213,12 +213,18 @@ public static class AppServiceCollectionExtensions | |||||||
|         // 缓存 |         // 缓存 | ||||||
|         if (cacheOptions.CacheType == CacheType.Memory) |         if (cacheOptions.CacheType == CacheType.Memory) | ||||||
|         { |         { | ||||||
|             services.AddSingleton<ICache, MemoryCache>(a => new() |             services.AddSingleton<ICache>(a => | ||||||
|             { |             { | ||||||
|                 Capacity = cacheOptions.MemoryCacheOptions.Capacity, |                 Cache.Default = new MemoryCache() | ||||||
|                 Expire = cacheOptions.MemoryCacheOptions.Expire, |                 { | ||||||
|                 Period = cacheOptions.MemoryCacheOptions.Period |                     Capacity = cacheOptions.MemoryCacheOptions.Capacity, | ||||||
|             }); |                     Expire = cacheOptions.MemoryCacheOptions.Expire, | ||||||
|  |                     Period = cacheOptions.MemoryCacheOptions.Period | ||||||
|  |                 }; | ||||||
|  |                 return Cache.Default; | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         } |         } | ||||||
|         else if (cacheOptions.CacheType == CacheType.Redis) |         else if (cacheOptions.CacheType == CacheType.Redis) | ||||||
|         { |         { | ||||||
| @@ -278,7 +284,6 @@ public static class AppServiceCollectionExtensions | |||||||
|                     && u.GetParameters().Length > 0 |                     && u.GetParameters().Length > 0 | ||||||
|                     && u.GetParameters().First().ParameterType == typeof(IServiceCollection)); |                     && u.GetParameters().First().ParameterType == typeof(IServiceCollection)); | ||||||
|  |  | ||||||
|             if (!serviceMethods.Any()) continue; |  | ||||||
|  |  | ||||||
|             // 自动安装属性调用 |             // 自动安装属性调用 | ||||||
|             foreach (var method in serviceMethods) |             foreach (var method in serviceMethods) | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
| using Microsoft.AspNetCore.Hosting; | using Microsoft.AspNetCore.Hosting; | ||||||
|  |  | ||||||
| using ThingsGateway; | using ThingsGateway; | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extension; | ||||||
| using ThingsGateway.Reflection; | using ThingsGateway.Reflection; | ||||||
|  |  | ||||||
| namespace Microsoft.Extensions.Hosting; | namespace Microsoft.Extensions.Hosting; | ||||||
| @@ -44,7 +44,7 @@ public static class HostBuilderExtensions | |||||||
|  |  | ||||||
|         hostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, combineAssembliesName); |         hostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, combineAssembliesName); | ||||||
|  |  | ||||||
|         // 实现假的 Starup,解决泛型主机启动问题 |         // 实现假的 Startup,解决泛型主机启动问题 | ||||||
|         hostBuilder.UseStartup<FakeStartup>(); |         hostBuilder.UseStartup<FakeStartup>(); | ||||||
|         return hostBuilder; |         return hostBuilder; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -18,7 +18,9 @@ using System.Runtime.CompilerServices; | |||||||
| using System.Text.Json; | using System.Text.Json; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Extensions; | using ThingsGateway.NewLife; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Extension; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 对象拓展类 | /// 对象拓展类 | ||||||
| @@ -26,70 +28,10 @@ namespace ThingsGateway.Extensions; | |||||||
| [SuppressSniffer] | [SuppressSniffer] | ||||||
| public static class ObjectExtensions | public static class ObjectExtensions | ||||||
| { | { | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTimeOffset 转换成本地 DateTime |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTime ConvertToDateTime(this DateTimeOffset dateTime) |  | ||||||
|     { |  | ||||||
|         if (dateTime.Offset.Equals(TimeSpan.Zero)) |  | ||||||
|             return dateTime.UtcDateTime; |  | ||||||
|         if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime))) |  | ||||||
|             return dateTime.ToLocalTime().DateTime; |  | ||||||
|         else |  | ||||||
|             return dateTime.DateTime; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTimeOffset? 转换成本地 DateTime? |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTime? ConvertToDateTime(this DateTimeOffset? dateTime) |  | ||||||
|     { |  | ||||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTime() : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTime 转换成 DateTimeOffset |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime) |  | ||||||
|     { |  | ||||||
|         return DateTime.SpecifyKind(dateTime, DateTimeKind.Local); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将 DateTime? 转换成 DateTimeOffset? |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="dateTime"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static DateTimeOffset? ConvertToDateTimeOffset(this DateTime? dateTime) |  | ||||||
|     { |  | ||||||
|         return dateTime.HasValue ? dateTime.Value.ConvertToDateTimeOffset() : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 将时间戳转换为 DateTime |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="timestamp"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     internal static DateTime ConvertToDateTime(this long timestamp) |  | ||||||
|     { |  | ||||||
|         var timeStampDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); |  | ||||||
|         var digitCount = (int)Math.Floor(Math.Log10(timestamp) + 1); |  | ||||||
|  |  | ||||||
|         if (digitCount != 13 && digitCount != 10) |  | ||||||
|         { |  | ||||||
|             throw new ArgumentException("Data is not a valid timestamp format."); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return (digitCount == 13 |  | ||||||
|             ? timeStampDateTime.AddMilliseconds(timestamp)  // 13 位时间戳 |  | ||||||
|             : timeStampDateTime.AddSeconds(timestamp)).ToLocalTime();   // 10 位时间戳 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 将 IFormFile 转换成 byte[] |     /// 将 IFormFile 转换成 byte[] | ||||||
| @@ -187,7 +129,7 @@ public static class ObjectExtensions | |||||||
|             { |             { | ||||||
|                 if (current == last) func(arg); |                 if (current == last) func(arg); | ||||||
|                 task.Dispose(); |                 task.Dispose(); | ||||||
|             }); |             }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -207,8 +149,8 @@ public static class ObjectExtensions | |||||||
|             Task.Delay(milliseconds).ContinueWith(task => |             Task.Delay(milliseconds).ContinueWith(task => | ||||||
|             { |             { | ||||||
|                 if (current == last) func(); |                 if (current == last) func(); | ||||||
|                 task.Dispose(); |                 task.TryDispose(); | ||||||
|             }); |             }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -263,7 +205,7 @@ public static class ObjectExtensions | |||||||
|     /// <typeparam name="T"></typeparam> |     /// <typeparam name="T"></typeparam> | ||||||
|     /// <param name="dic">字典</param> |     /// <param name="dic">字典</param> | ||||||
|     /// <param name="newDic">新字典</param> |     /// <param name="newDic">新字典</param> | ||||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) |     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||||
|     { |     { | ||||||
|         foreach (var (key, value) in newDic) |         foreach (var (key, value) in newDic) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ public class StartupFilter : IStartupFilter | |||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     // 输出当前环境标识 |                     // 输出当前环境标识 | ||||||
|                     context.Response.Headers["environment"] = envName; |                     context.Response.Headers["Environment"] = envName; | ||||||
|  |  | ||||||
|                     // 输出框架版本 |                     // 输出框架版本 | ||||||
|                     context.Response.Headers[nameof(ThingsGateway)] = version; |                     context.Response.Headers[nameof(ThingsGateway)] = version; | ||||||
|   | |||||||
| @@ -85,11 +85,14 @@ internal static class InternalApp | |||||||
|             // 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题 |             // 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题 | ||||||
|             services.AddHostedService<GenericHostLifetimeEventsHostedService>(); |             services.AddHostedService<GenericHostLifetimeEventsHostedService>(); | ||||||
|  |  | ||||||
|             // 注册 Startup 过滤器 |             if (WebEnableVariable.WebEnable == true) | ||||||
|             services.AddTransient<IStartupFilter, StartupFilter>(); |             { | ||||||
|  |                 // 注册 Startup 过滤器 | ||||||
|  |                 services.AddTransient<IStartupFilter, StartupFilter>(); | ||||||
|  |  | ||||||
|             // 注册 HttpContextAccessor 服务 |                 // 注册 HttpContextAccessor 服务 | ||||||
|             services.AddHttpContextAccessor(); |                 services.AddHttpContextAccessor(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // 初始化应用服务 |             // 初始化应用服务 | ||||||
|             services.AddApp(); |             services.AddApp(); | ||||||
| @@ -212,6 +215,10 @@ internal static class InternalApp | |||||||
|         // 获取环境变量名,如果没找到,则读取 NETCORE_ENVIRONMENT 环境变量信息识别(用于非 Web 环境) |         // 获取环境变量名,如果没找到,则读取 NETCORE_ENVIRONMENT 环境变量信息识别(用于非 Web 环境) | ||||||
|         var envName = hostEnvironment?.EnvironmentName ?? Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "Unknown"; |         var envName = hostEnvironment?.EnvironmentName ?? Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "Unknown"; | ||||||
|  |  | ||||||
|  |         // 获取 JSON 文件扫描配置(2025.07.25),修复 docker 中挂载大文件数据卷导致启动缓慢的问题 | ||||||
|  |         var jsonFileScanner = configuration.GetSection("AppSettings:JsonFileScanner") | ||||||
|  |             .Get<JsonFileScanner>() ?? new JsonFileScanner(); | ||||||
|  |  | ||||||
|         // 读取忽略的配置文件 |         // 读取忽略的配置文件 | ||||||
|         var ignoreConfigurationFiles = (configuration.GetSection("IgnoreConfigurationFiles") |         var ignoreConfigurationFiles = (configuration.GetSection("IgnoreConfigurationFiles") | ||||||
|                 .Get<string[]>() |                 .Get<string[]>() | ||||||
| @@ -237,7 +244,7 @@ internal static class InternalApp | |||||||
|             // 循环加载 |             // 循环加载 | ||||||
|             foreach (var jsonFile in files) |             foreach (var jsonFile in files) | ||||||
|             { |             { | ||||||
|                 configurationBuilder.AddJsonFile(jsonFile, optional: true, reloadOnChange: true); |                 configurationBuilder.AddJsonFile(jsonFile, optional: jsonFileScanner.Optional, reloadOnChange: jsonFileScanner.ReloadOnChange); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ namespace ThingsGateway; | |||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions> | public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions> | ||||||
| { | { | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否启用规范化文档 |     /// 是否启用规范化文档 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -50,7 +51,10 @@ public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions | |||||||
|     /// 【部署】二级虚拟目录 |     /// 【部署】二级虚拟目录 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public string VirtualPath { get; set; } |     public string VirtualPath { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// JSON 文件扫描配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public JsonFileScanner JsonFileScanner { get; set; } | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 后期配置 |     /// 后期配置 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -66,3 +70,20 @@ public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions | |||||||
|         options.VirtualPath ??= string.Empty; |         options.VirtualPath ??= string.Empty; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// JSON 文件扫描配置 | ||||||
|  | /// </summary> | ||||||
|  | /// <remarks>修复 docker 中挂载大文件数据卷导致启动缓慢的问题。</remarks> | ||||||
|  | public class JsonFileScanner | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 是否可选 | ||||||
|  |     /// </summary> | ||||||
|  |     public bool Optional { get; set; } = true; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 是否改变的时候重载 | ||||||
|  |     /// </summary> | ||||||
|  |     public bool ReloadOnChange { get; set; } = true; | ||||||
|  | } | ||||||
							
								
								
									
										341
									
								
								src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,341 @@ | |||||||
|  | // ------------------------------------------------------------------------ | ||||||
|  | // 版权信息 | ||||||
|  | // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||||
|  | // 所有权利保留。 | ||||||
|  | // 官方网站:https://baiqian.com | ||||||
|  | // | ||||||
|  | // 许可证信息 | ||||||
|  | // 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。 | ||||||
|  | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
|  | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Builder; | ||||||
|  | using Microsoft.AspNetCore.Hosting; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Microsoft.Extensions.Hosting; | ||||||
|  |  | ||||||
|  | using ThingsGateway; | ||||||
|  |  | ||||||
|  | namespace System; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <see cref="WebApplication"/> 方式配置选项 | ||||||
|  | /// </summary> | ||||||
|  | [SuppressSniffer] | ||||||
|  | public sealed class MiniRunOptions : IRunOptions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 内部构造函数 | ||||||
|  |     /// </summary> | ||||||
|  |     internal MiniRunOptions() | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions Default { get; } = new MiniRunOptions(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置(带启动参数) | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions Main(string[] args) | ||||||
|  |     { | ||||||
|  |         return Default.WithArgs(args); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置(静默启动) | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions DefaultSilence { get; } = new MiniRunOptions().Silence(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置(静默启动 + 启动参数) | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions MainSilence(string[] args) | ||||||
|  |     { | ||||||
|  |         return DefaultSilence.WithArgs(args); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="WebApplicationOptions"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="options"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureOptions(WebApplicationOptions options) | ||||||
|  |     { | ||||||
|  |         Options = options; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="IWebHostBuilder"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureBuilder(Action<IWebHostBuilder> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionBuilder = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="IHostBuilder"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureFirstActionBuilder(Action<IHostBuilder> configureAction) | ||||||
|  |     { | ||||||
|  |         FirstActionBuilder = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="IServiceCollection"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureServices(Action<IServiceCollection> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionServices = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="InjectOptions"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureInject(Action<IWebHostBuilder, InjectOptions> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionInject = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="WebApplication"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction">配置委托</param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions Configure(Action<IHost> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionConfigure = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="ConfigurationManager"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction">配置委托</param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureConfiguration(Action<IHostEnvironment, IConfiguration> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionConfigurationManager = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddComponent<TComponent>() | ||||||
|  |         where TComponent : class, IServiceComponent, new() | ||||||
|  |     { | ||||||
|  |         ServiceComponents.Add(typeof(TComponent), null); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <typeparam name="TComponentOptions"></typeparam> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddComponent<TComponent, TComponentOptions>(TComponentOptions options) | ||||||
|  |         where TComponent : class, IServiceComponent, new() | ||||||
|  |     { | ||||||
|  |         ServiceComponents.Add(typeof(TComponent), options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="componentType">组件类型</param> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddComponent(Type componentType, object options) | ||||||
|  |     { | ||||||
|  |         ServiceComponents.Add(componentType, options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions UseComponent<TComponent>() | ||||||
|  |         where TComponent : class, IApplicationComponent, new() | ||||||
|  |     { | ||||||
|  |         ApplicationComponents.Add(typeof(TComponent), null); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <typeparam name="TComponentOptions"></typeparam> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions UseComponent<TComponent, TComponentOptions>(TComponentOptions options) | ||||||
|  |         where TComponent : class, IApplicationComponent, new() | ||||||
|  |     { | ||||||
|  |         ApplicationComponents.Add(typeof(TComponent), options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="componentType">组件类型</param> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions UseComponent(Type componentType, object options) | ||||||
|  |     { | ||||||
|  |         ApplicationComponents.Add(componentType, options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加 IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddWebComponent<TComponent>() | ||||||
|  |         where TComponent : class, IWebComponent, new() | ||||||
|  |     { | ||||||
|  |         WebComponents.Add(typeof(TComponent), null); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加 IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <typeparam name="TComponentOptions"></typeparam> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddWebComponent<TComponent, TComponentOptions>(TComponentOptions options) | ||||||
|  |         where TComponent : class, IWebComponent, new() | ||||||
|  |     { | ||||||
|  |         WebComponents.Add(typeof(TComponent), options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加 IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="componentType">组件类型</param> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddWebComponent(Type componentType, object options) | ||||||
|  |     { | ||||||
|  |         WebComponents.Add(componentType, options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 标识主机静默启动 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>不阻塞程序运行</remarks> | ||||||
|  |     /// <param name="silence">静默启动</param> | ||||||
|  |     /// <param name="logging">静默启动日志状态,默认 false</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions Silence(bool silence = true, bool logging = false) | ||||||
|  |     { | ||||||
|  |         IsSilence = silence; | ||||||
|  |         SilenceLogging = logging; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 设置进程启动参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="args">启动参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions WithArgs(string[] args) | ||||||
|  |     { | ||||||
|  |         Args = args; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// <see cref="WebApplicationOptions"/> | ||||||
|  |     /// </summary> | ||||||
|  |     internal WebApplicationOptions Options { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IServiceCollection"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IServiceCollection> ActionServices { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IWebHostBuilder"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IHostBuilder> FirstActionBuilder { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IWebHostBuilder"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IWebHostBuilder> ActionBuilder { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="InjectOptions"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IWebHostBuilder, InjectOptions> ActionInject { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IHost"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IHost> ActionConfigure { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IConfiguration"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IHostEnvironment, IConfiguration> ActionConfigurationManager { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Dictionary<Type, object> ServiceComponents { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Dictionary<Type, object> WebComponents { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Dictionary<Type, object> ApplicationComponents { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 静默启动 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>不阻塞程序运行</remarks> | ||||||
|  |     internal bool IsSilence { get; private set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 静默启动日志状态 | ||||||
|  |     /// </summary> | ||||||
|  |     internal bool SilenceLogging { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 命令行参数 | ||||||
|  |     /// </summary> | ||||||
|  |     internal string[] Args { get; set; } | ||||||
|  | } | ||||||
| @@ -602,6 +602,33 @@ public static class Serve | |||||||
|         return app; |         return app; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 启动 WebApplication 主机 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>未包含 Web 基础功能,需手动注册服务/中间件</remarks> | ||||||
|  |     /// <param name="options">配置选项</param> | ||||||
|  |     /// <param name="urls">默认 5000/5001 端口</param> | ||||||
|  |     /// <param name="cancellationToken"></param> | ||||||
|  |     /// <returns><see cref="IHost"/></returns> | ||||||
|  |     public static async Task<IHost> RunAsync(MiniRunOptions options, string urls = default, CancellationToken cancellationToken = default) | ||||||
|  |     { | ||||||
|  |         // 构建 WebApplication 对象 | ||||||
|  |         BuildMiniApplication(options, urls, out var app); | ||||||
|  |  | ||||||
|  |         // 是否静默启动 | ||||||
|  |         if (!options.IsSilence) | ||||||
|  |         { | ||||||
|  |             // 配置启动地址和端口 | ||||||
|  |             await app.RunAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             await app.StartAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构建 WebApplication 对象 |     /// 构建 WebApplication 对象 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -616,8 +643,8 @@ public static class Serve | |||||||
|  |  | ||||||
|         // 初始化 WebApplicationBuilder |         // 初始化 WebApplicationBuilder | ||||||
|         var builder = (options.Options == null |         var builder = (options.Options == null | ||||||
|             ? WebApplication.CreateBuilder(args) |            ? WebApplication.CreateBuilder(args) | ||||||
|             : WebApplication.CreateBuilder(options.Options)); |            : WebApplication.CreateBuilder(options.Options)); | ||||||
|  |  | ||||||
|         // 调用自定义配置服务 |         // 调用自定义配置服务 | ||||||
|         options?.FirstActionBuilder?.Invoke(builder); |         options?.FirstActionBuilder?.Invoke(builder); | ||||||
| @@ -674,7 +701,7 @@ public static class Serve | |||||||
|         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); |         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); | ||||||
|  |  | ||||||
|         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); |         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); | ||||||
|         // 配置所有 Starup Configure |         // 配置所有 Startup Configure | ||||||
|         UseStartups(app); |         UseStartups(app); | ||||||
|         UseStartups(app.Services); |         UseStartups(app.Services); | ||||||
|  |  | ||||||
| @@ -793,12 +820,138 @@ public static class Serve | |||||||
|         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); |         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); | ||||||
|  |  | ||||||
|         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); |         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); | ||||||
|         // 配置所有 Starup Configure |         // 配置所有 Startup Configure | ||||||
|         UseStartups(app.Services); |         UseStartups(app.Services); | ||||||
|         // 释放内存 |         // 释放内存 | ||||||
|         App.AppStartups.Clear(); |         App.AppStartups.Clear(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 构建 IHost 对象 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="options">配置选项</param> | ||||||
|  |     /// <param name="urls">默认 5000/5001 端口</param> | ||||||
|  |     /// <param name="app"><see cref="IHost"/></param> | ||||||
|  |     public static void BuildMiniApplication(MiniRunOptions options, string urls, out IHost app) | ||||||
|  |     { | ||||||
|  |         // 获取命令行参数 | ||||||
|  |         var args = options.Args ?? Environment.GetCommandLineArgs().Skip(1).ToArray(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         var builder = Host.CreateDefaultBuilder(args); | ||||||
|  |  | ||||||
|  |         // 静默启动排除指定日志类名 | ||||||
|  |         if (options.IsSilence && !options.SilenceLogging) | ||||||
|  |         { | ||||||
|  |             builder = builder.ConfigureLogging(logging => | ||||||
|  |             { | ||||||
|  |                 logging.AddFilter((provider, category, logLevel) => !SilenceExcludesOfLogCategoryName.Any(u => category.StartsWith(u))); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // 配置 Web 主机 | ||||||
|  |         builder = builder.ConfigureWebHost(webHostBuilder => | ||||||
|  |         { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             // 调用自定义配置服务 | ||||||
|  |             options?.FirstActionBuilder?.Invoke(builder); | ||||||
|  |  | ||||||
|  |             // 注册 WebApplicationBuilder 组件 | ||||||
|  |             if (options.WebComponents.Count > 0) | ||||||
|  |             { | ||||||
|  |                 foreach (var (componentType, opt) in options.WebComponents) | ||||||
|  |                 { | ||||||
|  |                     webHostBuilder.AddWebComponent(componentType, opt); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             webHostBuilder.Configure((WebHostBuilderContext app, IApplicationBuilder applicationBuilder) => | ||||||
|  |             { | ||||||
|  |  | ||||||
|  |                 // 添加自定义配置 | ||||||
|  |                 options.ActionConfigurationManager?.Invoke(app.HostingEnvironment, app.Configuration); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             // 初始化框架 | ||||||
|  |             webHostBuilder.Inject(options.ActionInject); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             // 配置服务 | ||||||
|  |             if (options.ServiceComponents.Count > 0) | ||||||
|  |             { | ||||||
|  |                 webHostBuilder = webHostBuilder.ConfigureServices(services => | ||||||
|  |                 { | ||||||
|  |                     // 注册应用服务组件 | ||||||
|  |                     foreach (var (componentType, opt) in options.ServiceComponents) | ||||||
|  |                     { | ||||||
|  |                         services.AddComponent(componentType, opt); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 配置启动地址和端口 | ||||||
|  |             var startUrls = !string.IsNullOrWhiteSpace(urls) ? urls : webHostBuilder.GetSetting(nameof(urls)); | ||||||
|  |  | ||||||
|  |             // 自定义启动端口 | ||||||
|  |             if (!string.IsNullOrWhiteSpace(startUrls)) | ||||||
|  |             { | ||||||
|  |                 webHostBuilder = webHostBuilder.UseUrls(startUrls); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             // 调用自定义配置 | ||||||
|  |             options?.ActionBuilder?.Invoke(webHostBuilder); | ||||||
|  |  | ||||||
|  |             // 配置中间件 | ||||||
|  |             if (options.ApplicationComponents.Count > 0) | ||||||
|  |             { | ||||||
|  |                 webHostBuilder = webHostBuilder.Configure((context, app) => | ||||||
|  |                 { | ||||||
|  |                     // 注册应用中间件组件 | ||||||
|  |                     foreach (var (componentType, opt) in options.ApplicationComponents) | ||||||
|  |                     { | ||||||
|  |                         app.UseComponent(context.HostingEnvironment, componentType, opt); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         builder = builder.ConfigureServices(services => | ||||||
|  |         { | ||||||
|  |             // 调用自定义配置服务 | ||||||
|  |             options?.ActionServices?.Invoke(services); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 构建主机 | ||||||
|  |         app = builder.Build(); | ||||||
|  |  | ||||||
|  |         InternalApp.RootServices ??= app.Services; | ||||||
|  |  | ||||||
|  |         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); | ||||||
|  |  | ||||||
|  |         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); | ||||||
|  |         // 配置所有 Startup Configure | ||||||
|  |         UseStartups(app.Services); | ||||||
|  |         // 释放内存 | ||||||
|  |         App.AppStartups.Clear(); | ||||||
|  |         // 调用自定义配置 | ||||||
|  |         options?.ActionConfigure?.Invoke(app); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构建 IHost 对象 |     /// 构建 IHost 对象 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -852,7 +1005,7 @@ public static class Serve | |||||||
|         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); |         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); | ||||||
|         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); |         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); | ||||||
|  |  | ||||||
|         // 配置所有 Starup Configure |         // 配置所有 Startup Configure | ||||||
|         UseStartups(app.Services); |         UseStartups(app.Services); | ||||||
|         // 释放内存 |         // 释放内存 | ||||||
|         App.AppStartups.Clear(); |         App.AppStartups.Clear(); | ||||||
| @@ -908,7 +1061,6 @@ public static class Serve | |||||||
|                     && u.GetParameters().Length > 0 |                     && u.GetParameters().Length > 0 | ||||||
|                     && u.GetParameters().First().ParameterType == typeof(IServiceProvider)); |                     && u.GetParameters().First().ParameterType == typeof(IServiceProvider)); | ||||||
|  |  | ||||||
|             if (!configureMethods.Any()) continue; |  | ||||||
|  |  | ||||||
|             // 自动安装属性调用 |             // 自动安装属性调用 | ||||||
|             foreach (var method in configureMethods) |             foreach (var method in configureMethods) | ||||||
| @@ -935,7 +1087,6 @@ public static class Serve | |||||||
|                     && u.GetParameters().Length > 0 |                     && u.GetParameters().Length > 0 | ||||||
|                     && u.GetParameters().First().ParameterType == typeof(IApplicationBuilder)); |                     && u.GetParameters().First().ParameterType == typeof(IApplicationBuilder)); | ||||||
|  |  | ||||||
|             if (!configureMethods.Any()) continue; |  | ||||||
|  |  | ||||||
|             // 自动安装属性调用 |             // 自动安装属性调用 | ||||||
|             foreach (var method in configureMethods) |             foreach (var method in configureMethods) | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| using Microsoft.AspNetCore.Builder; | using Microsoft.AspNetCore.Builder; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| #pragma warning disable CA1822 // 将成员标记为 static |  | ||||||
|  |  | ||||||
| namespace ThingsGateway; | namespace ThingsGateway; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | |||||||
|     /// <param name="mvcBuilder"></param> |     /// <param name="mvcBuilder"></param> | ||||||
|     /// <param name="configure"></param> |     /// <param name="configure"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<ConcurrentDictionary<Type, Type>> configure = default) |     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||||
|     { |     { | ||||||
|         mvcBuilder.Services.AddFromConvertBinding(configure); |         mvcBuilder.Services.AddFromConvertBinding(configure); | ||||||
|  |  | ||||||
| @@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | |||||||
|     /// <param name="services"></param> |     /// <param name="services"></param> | ||||||
|     /// <param name="configure"></param> |     /// <param name="configure"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<ConcurrentDictionary<Type, Type>> configure = default) |     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||||
|     { |     { | ||||||
|         // 非 Web 环境跳过注册 |         // 非 Web 环境跳过注册 | ||||||
|         if (App.WebHostEnvironment == default) return services; |         if (App.WebHostEnvironment == default) return services; | ||||||
|  |  | ||||||
|         // 定义模型绑定转换器集合 |         // 定义模型绑定转换器集合 | ||||||
|         var modelBinderConverts = new ConcurrentDictionary<Type, Type>(); |         var modelBinderConverts = new NonBlockingDictionary<Type, Type>(); | ||||||
|         modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); |         modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); | ||||||
|         modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); |         modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extension; | ||||||
|  |  | ||||||
| namespace ThingsGateway.AspNetCore; | namespace ThingsGateway.AspNetCore; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 定义模型绑定转换器集合 |     /// 定义模型绑定转换器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; |     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> |     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||||
|     public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts) |     public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||||
|     { |     { | ||||||
|         _modelBinderConverts = modelBinderConverts; |         _modelBinderConverts = modelBinderConverts; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 定义模型绑定转换器集合 |     /// 定义模型绑定转换器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; |     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> |     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||||
|     public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts) |     public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||||
|     { |     { | ||||||
|         _modelBinderConverts = modelBinderConverts; |         _modelBinderConverts = modelBinderConverts; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extension; | ||||||
|  |  | ||||||
| namespace ThingsGateway.AspNetCore; | namespace ThingsGateway.AspNetCore; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||||
| using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; | using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extension; | ||||||
|  |  | ||||||
| namespace ThingsGateway.AspNetCore; | namespace ThingsGateway.AspNetCore; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ using System.Reflection; | |||||||
|  |  | ||||||
| using ThingsGateway; | using ThingsGateway; | ||||||
| using ThingsGateway.ConfigurableOptions; | using ThingsGateway.ConfigurableOptions; | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extension; | ||||||
|  |  | ||||||
| namespace Microsoft.Extensions.DependencyInjection; | namespace Microsoft.Extensions.DependencyInjection; | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user