mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 07:33:58 +08:00 
			
		
		
		
	Compare commits
	
		
			308 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c6aec3a1af | ||
|   | 22e30f7a62 | ||
|   | 57711b8ab5 | ||
|   | 90ff1259ea | ||
|   | d88fc5ccd7 | ||
|   | 5aaca2aa9c | ||
|   | 8b9ca56e17 | ||
|   | e4f3772e6d | ||
|   | d58ec81d20 | ||
|   | 415aae44b6 | ||
|   | a533286658 | ||
|   | e59f91cd82 | ||
|   | 5f8b85d8a4 | ||
|   | 47c7b88436 | ||
|   | 90006782f2 | ||
|   | c3d49cbe70 | ||
|   | 112323a360 | ||
|   | 9d08c90fda | ||
|   | 602d24deec | ||
|   | a2b9f66785 | ||
|   | 7cbf289b50 | ||
|   | 4097da79a5 | ||
|   | 91b7ae554f | ||
|   | 3121aa2542 | ||
|   | 4bf895e6e1 | ||
|   | 0c5489e920 | ||
|   | d63c3aaa80 | ||
|   | 4f188ea6cc | ||
|   | acb17018ae | ||
|   | 2affe2988d | ||
|   | 4174dd2206 | ||
|   | e1c492f238 | ||
|   | fb08e34fa3 | ||
|   | a1793a0afe | ||
|   | 4da9763b49 | ||
|   | 81e0918bd0 | ||
|   | c1e064f06d | ||
|   | 1c52be8b47 | ||
|   | bcd82055ca | ||
|   | c47d95d170 | ||
|   | 3e62f1ad51 | ||
|   | 8dcae973ef | ||
|   | 4cf35f7294 | ||
|   | 94c77d151b | ||
|   | 7f600e2b4b | ||
|   | c809d0ba87 | ||
|   | 50f038ec89 | ||
|   | 9199a255a2 | ||
|   | d324537b47 | ||
|   | d0c05685f7 | ||
|   | 1063c930b5 | ||
|   | 79cbd44366 | ||
|   | 7fdac1c5cb | ||
|   | 0c0cf72ebb | ||
|   | 8e2fe175ed | ||
|   | d1cff037c9 | ||
|   | fc88a2fafa | ||
|   | 45fcceb056 | ||
|   | 7043477038 | ||
|   | 7dd685cf54 | ||
|   | 5f5e4969c0 | ||
|   | 8a53fd19e9 | ||
|   | baf4714c36 | ||
|   | 7ba9ac7a5b | ||
|   | 85b8f26e8e | ||
|   | 594a0f1410 | ||
|   | d317d757d7 | ||
|   | fdf0ba6318 | ||
|   | 15bf7de5fa | ||
|   | d3402b058e | ||
|   | e7dfdd4031 | ||
|   | b2dd7b6364 | ||
|   | 9bd6d9abbf | ||
|   | cd14428fea | ||
|   | 19d9f03c2b | ||
|   | 0d57e72bbf | ||
|   | 329516a61b | ||
|   | d566869589 | ||
|   | 9cb8d8e6c7 | ||
|   | 9de3c57e5d | ||
|   | f32ff92b0b | ||
|   | 88d71e271e | ||
|   | fd9c14612a | ||
|   | e26e5a160f | ||
|   | b836bfed22 | ||
|   | a4b598c6d0 | ||
|   | c9ab755839 | ||
|   | 9920edba53 | ||
|   | 12bd7280d1 | ||
|   | d30ea7f63b | ||
|   | ebd3390db6 | ||
|   | 9a374a9ebc | ||
|   | b1bc22cb08 | ||
|   | 4930d53890 | ||
|   | c31327b5bc | ||
|   | 3f2aa1f1e1 | ||
|   | 6e78c00a96 | ||
|   | c27dde085e | ||
|   | d26cc308c0 | ||
|   | fb1efdf290 | ||
|   | 3c99f2a472 | ||
|   | affe9a44e0 | ||
|   | 43730fa519 | ||
|   | d39aa22b09 | ||
|   | e232a6b6ea | ||
|   | 71ebb36fe9 | ||
|   | 78a0b86327 | ||
|   | 2636c16a97 | ||
|   | fd77c0242d | ||
|   | e74819a900 | ||
|   | 9b7f696c9b | ||
|   | 0230d614e7 | ||
|   | 252d99ad78 | ||
|   | 1ffc200350 | ||
|   | 807d89b2b2 | ||
|   | 4013afa1f1 | ||
|   | a580927ceb | ||
|   | bf2cf52034 | ||
|   | 81bb8b7c31 | ||
|   | a825007fb5 | ||
|   | 988124d96a | ||
|   | f0de815296 | ||
|   | 0e2d58c887 | ||
|   | b155382626 | ||
|   | f362d740af | ||
|   | 4a85e31a4f | ||
|   | 302c270ad5 | ||
|   | 3c1517d0f3 | ||
|   | f9fb222044 | ||
|   | e8edc02ba3 | ||
|   | 95a44e3053 | ||
|   | 74a9fe9a87 | ||
|   | 4d03f9ea1a | ||
|   | 67c96ca991 | ||
|   | 88fb793c68 | ||
|   | d6d02d8cc5 | ||
|   | c5a3f8e2e3 | ||
|   | 27e8653a1a | ||
|   | 863beda82c | ||
|   | bac84c3ecd | ||
|   | 2fca2ad9f8 | ||
|   | dd75286fe0 | ||
|   | 7f91792cf1 | ||
|   | 0e0ccad311 | ||
|   | 0691f72e67 | ||
|   | 7e38a51720 | ||
|   | 34ca8243a3 | ||
|   | 112fea7632 | ||
|   | 378763e4ee | ||
|   | 517bd0394d | ||
|   | 70adb97fb5 | ||
|   | 623d44cabe | ||
|   | 0d479ca00b | ||
|   | 8bc49ef437 | ||
|   | f83fcec786 | ||
|   | 93690ce40d | ||
|   | f82c5f2f27 | ||
|   | a83c1c3899 | ||
|   | 91d6aed109 | ||
|   | db8f8fe51d | ||
|   | 4596004b17 | ||
|   | d5540906cb | ||
|   | 90796a979d | ||
|   | 2190a87772 | ||
|   | c5953b83f8 | ||
|   | 24bc60abf0 | ||
|   | 31eee6b009 | ||
|   | c5da565a8f | ||
|   | 947cd712e1 | ||
|   | edc208f96b | ||
|   | 1fb0296ee7 | ||
|   | 6488d3df87 | ||
|   | 56189d78e0 | ||
|   | bff18127b8 | ||
|   | 363206e0ba | ||
|   | fd3e378501 | ||
|   | 4ba2fe4c9d | ||
|   | 2c499626ad | ||
|   | 2b581a03c3 | ||
|   | 450c15210a | ||
|   | 65fed8cc93 | ||
|   | 4b64771ea2 | ||
|   | f39977a6ff | ||
|   | 933b535caa | ||
|   | 8abc5d2f20 | ||
|   | d8783cd994 | ||
|   | d5d087feb5 | ||
|   | 6ba3399df7 | ||
|   | 65124b3aa8 | ||
|   | 98597f4726 | ||
|   | e7981f0d8e | ||
|   | cf654427c3 | ||
|   | ff2f628282 | ||
|   | ae818ca265 | ||
|   | 0f2aed458e | ||
|   | d486c44ff6 | ||
|   | ca7b9980bf | ||
|   | 3c71e6a8e3 | ||
|   | 542442864c | ||
|   | 5edb64fa85 | ||
|   | 8dc1c898a3 | ||
|   | 1ed35726b0 | ||
|   | 27fae9ebaa | ||
|   | b103f25c94 | ||
|   | abff450274 | ||
|   | c260736a11 | ||
|   | 166ac2307a | ||
|   | b21a4e1a4d | ||
|   | f7dc943fa3 | ||
|   | bfbd2693ec | ||
|   | 819e71c993 | ||
|   | 9fd0b489a2 | ||
|   | f5fe9f8dae | ||
|   | f9ffc18145 | ||
|   | 08db5b983a | ||
|   | 5b3b4c8c50 | ||
|   | 73f914ffc4 | ||
|   | d6bdd73ed6 | ||
|   | 7370ee7349 | ||
|   | 4574596bac | ||
|   | 4d16855e36 | ||
|   | 13a0d4d282 | ||
|   | b9cd06b829 | ||
|   | 5b460e8fa2 | ||
|   | 41087edf17 | ||
|   | 2afcc38e38 | ||
|   | e59ccce25f | ||
|   | d7425890e8 | ||
|   | a989a837fb | ||
|   | db1221da50 | ||
|   | cf794569ed | ||
|   | 51e5bbab0d | ||
|   | 2c197ed2b2 | ||
|   | d8fc6665b3 | ||
|   | c671a79822 | ||
|   | 9d93ce4c41 | ||
|   | a6d99fe227 | ||
|   | 923b8bca31 | ||
|   | e2c30d1c88 | ||
|   | b6d9f2a04e | ||
|   | 57306ea664 | ||
|   | cd7f3fd02f | ||
|   | 0482e077a8 | ||
|   | 5f986a45ca | ||
|   | ca7b49c0d5 | ||
|   | 52dd555e6c | ||
|   | 579b1a59f9 | ||
|   | 5299c5c4be | ||
|   | f7756bccef | ||
|   | a6b874d160 | ||
|   | 3e5fb3ddcf | ||
|   | 5e6bcb12d3 | ||
|   | 14303f1429 | ||
|   | 96711ba022 | ||
|   | cbfc0fdbdc | ||
|   | 6e81886c0e | ||
|   | 2d976bc132 | ||
|   | 57f6a476af | ||
|   | 8491ed296e | ||
|   | cd1288afdc | ||
|   | ec6c830cb0 | ||
|   | 2f86ccc4bf | ||
|   | 8ca445aec0 | ||
|   | 1e1f27c8a5 | ||
|   | 2b84bde367 | ||
|   | b52e58551d | ||
|   | 9aceed00bf | ||
|   | 58814f7f74 | ||
|   | 6a70ef9f31 | ||
|   | 82cc4ca500 | ||
|   | 4567fa04ed | ||
|   | 8b98b5d818 | ||
|   | 176d0351af | ||
|   | d63dc3384b | ||
|   | 1ccd704e30 | ||
|   | f5d23dbe79 | ||
|   | 75bfe53ac3 | ||
|   | 3308f916dd | ||
|   | e7140279ca | ||
|   | 1034719f5e | ||
|   | 2c00043a7f | ||
|   | 65c695d9ce | ||
|   | 57253fe46a | ||
|   | 4e5c443440 | ||
|   | 0b3b73d8ec | ||
|   | 921eabc134 | ||
|   | 0faa428751 | ||
|   | f71a2fdd63 | ||
|   | 4eb9ed8aba | ||
|   | d7b549abb8 | ||
|   | 95d723c578 | ||
|   | 2fcd853e86 | ||
|   | 07eef7c812 | ||
|   | b01e0757fa | ||
|   | 32844a20c6 | ||
|   | 5b6532c601 | ||
|   | 2c5b4b4027 | ||
|   | 72d7ecf195 | ||
|   | 2cfa6b4306 | ||
|   | 6f6ffde0ab | ||
|   | 1694739a16 | ||
|   | 95d1e8bfca | ||
|   | 60dec08e3c | ||
|   | a99d71be93 | ||
|   | f1331b6a0c | ||
|   | 10d66b642b | ||
|   | cd2310e4a8 | ||
|   | 1b399cf6b0 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -363,4 +363,6 @@ MigrationBackup/ | ||||
| FodyWeavers.xsd | ||||
|  | ||||
|  | ||||
| /framework/*pro* | ||||
| /framework/*Pro* | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|  | ||||
|  **ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) | ||||
|  | ||||
|  **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln) | ||||
|  **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin** | ||||
|  | ||||
|  | ||||
| ## 文档 | ||||
| @@ -22,7 +22,9 @@ | ||||
|  | ||||
| [ThingsGateway演示地址](http://120.24.62.140:5000/) | ||||
|  | ||||
| 账户	:  **superAdmin**	密码 : **111111** | ||||
| 账户	:  **superAdmin**	 | ||||
|  | ||||
| 密码 : **111111** | ||||
|  | ||||
| ## 赞助 | ||||
|  | ||||
|   | ||||
| @@ -1,149 +0,0 @@ | ||||
| [*.cs] | ||||
|  | ||||
| # CA1822: 将成员标记为 static | ||||
| dotnet_diagnostic.CA1822.severity = none | ||||
|  | ||||
| # CA1816: Dispose 方法应调用 SuppressFinalize | ||||
| dotnet_diagnostic.CA1816.severity = none | ||||
|  | ||||
| # CA2254: 模板应为静态表达式 | ||||
| dotnet_diagnostic.CA2254.severity = none | ||||
|  | ||||
| [*.cs] | ||||
| #### 命名样式 #### | ||||
|  | ||||
| # 命名规则 | ||||
|  | ||||
| dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion | ||||
| dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface | ||||
| dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i | ||||
|  | ||||
| dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion | ||||
| dotnet_naming_rule.types_should_be_pascal_case.symbols = types | ||||
| dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case | ||||
|  | ||||
| dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion | ||||
| dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members | ||||
| dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case | ||||
|  | ||||
| # 符号规范 | ||||
|  | ||||
| dotnet_naming_symbols.interface.applicable_kinds = interface | ||||
| dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected | ||||
| dotnet_naming_symbols.interface.required_modifiers =  | ||||
|  | ||||
| dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum | ||||
| dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected | ||||
| dotnet_naming_symbols.types.required_modifiers =  | ||||
|  | ||||
| dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method | ||||
| dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected | ||||
| dotnet_naming_symbols.non_field_members.required_modifiers =  | ||||
|  | ||||
| # 命名样式 | ||||
|  | ||||
| dotnet_naming_style.begins_with_i.required_prefix = I | ||||
| dotnet_naming_style.begins_with_i.required_suffix =  | ||||
| dotnet_naming_style.begins_with_i.word_separator =  | ||||
| dotnet_naming_style.begins_with_i.capitalization = pascal_case | ||||
|  | ||||
| dotnet_naming_style.pascal_case.required_prefix =  | ||||
| dotnet_naming_style.pascal_case.required_suffix =  | ||||
| dotnet_naming_style.pascal_case.word_separator =  | ||||
| dotnet_naming_style.pascal_case.capitalization = pascal_case | ||||
|  | ||||
| dotnet_naming_style.pascal_case.required_prefix =  | ||||
| dotnet_naming_style.pascal_case.required_suffix =  | ||||
| dotnet_naming_style.pascal_case.word_separator =  | ||||
| dotnet_naming_style.pascal_case.capitalization = pascal_case | ||||
| csharp_using_directive_placement = outside_namespace:silent | ||||
| csharp_style_expression_bodied_methods = false:silent | ||||
| csharp_style_expression_bodied_constructors = false:silent | ||||
| csharp_style_expression_bodied_operators = false:silent | ||||
| csharp_style_expression_bodied_properties = true:silent | ||||
| csharp_style_expression_bodied_indexers = true:silent | ||||
| csharp_style_expression_bodied_accessors = true:silent | ||||
| csharp_style_expression_bodied_lambdas = true:silent | ||||
| csharp_style_expression_bodied_local_functions = false:silent | ||||
| csharp_style_conditional_delegate_call = true:suggestion | ||||
| csharp_style_var_for_built_in_types = false:silent | ||||
| csharp_style_var_when_type_is_apparent = false:silent | ||||
| csharp_style_var_elsewhere = false:silent | ||||
| csharp_prefer_simple_using_statement = true:suggestion | ||||
| csharp_prefer_braces = true:silent | ||||
| csharp_style_namespace_declarations = block_scoped:silent | ||||
| csharp_style_prefer_method_group_conversion = true:silent | ||||
| csharp_style_prefer_top_level_statements = true:silent | ||||
|  | ||||
| # CA2208: 正确实例化参数异常 | ||||
| dotnet_diagnostic.CA2208.severity = none | ||||
|  | ||||
| # IDE0057: 使用范围运算符 | ||||
| dotnet_diagnostic.IDE0057.severity = none | ||||
|  | ||||
| # IDE0056: 使用索引运算符 | ||||
| dotnet_diagnostic.IDE0056.severity = none | ||||
|  | ||||
| # CA1830: 最好使用 StringBuilder 的强类型 Append 和 Insert 方法重载 | ||||
| dotnet_diagnostic.CA1830.severity = none | ||||
|  | ||||
| # CA1847: 将字符型文本用于单个字符查找 | ||||
| dotnet_diagnostic.CA1847.severity = none | ||||
|  | ||||
| [*.vb] | ||||
| #### 命名样式 #### | ||||
|  | ||||
| # 命名规则 | ||||
|  | ||||
| dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion | ||||
| dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface | ||||
| dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 | ||||
|  | ||||
| dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion | ||||
| dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 | ||||
| dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 | ||||
|  | ||||
| dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion | ||||
| dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 | ||||
| dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 | ||||
|  | ||||
| # 符号规范 | ||||
|  | ||||
| dotnet_naming_symbols.interface.applicable_kinds = interface | ||||
| dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected | ||||
| dotnet_naming_symbols.interface.required_modifiers =  | ||||
|  | ||||
| dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum | ||||
| dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected | ||||
| dotnet_naming_symbols.类型.required_modifiers =  | ||||
|  | ||||
| dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method | ||||
| dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected | ||||
| dotnet_naming_symbols.非字段成员.required_modifiers =  | ||||
|  | ||||
| # 命名样式 | ||||
|  | ||||
| dotnet_naming_style.以_i_开始.required_prefix = I | ||||
| dotnet_naming_style.以_i_开始.required_suffix =  | ||||
| dotnet_naming_style.以_i_开始.word_separator =  | ||||
| dotnet_naming_style.以_i_开始.capitalization = pascal_case | ||||
|  | ||||
| dotnet_naming_style.帕斯卡拼写法.required_prefix =  | ||||
| dotnet_naming_style.帕斯卡拼写法.required_suffix =  | ||||
| dotnet_naming_style.帕斯卡拼写法.word_separator =  | ||||
| dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case | ||||
|  | ||||
| dotnet_naming_style.帕斯卡拼写法.required_prefix =  | ||||
| dotnet_naming_style.帕斯卡拼写法.required_suffix =  | ||||
| dotnet_naming_style.帕斯卡拼写法.word_separator =  | ||||
| dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case | ||||
|  | ||||
| [*.{cs,vb}] | ||||
| dotnet_style_qualification_for_field = false:silent | ||||
| dotnet_style_qualification_for_property = false:silent | ||||
| dotnet_style_qualification_for_method = false:silent | ||||
| dotnet_style_qualification_for_event = false:silent | ||||
| end_of_line = crlf | ||||
|  | ||||
| # IDE0060: 删除未使用的参数 | ||||
| dotnet_diagnostic.IDE0060.severity = none | ||||
| @@ -1,30 +0,0 @@ | ||||
| <Project> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net6.0;net7.0</TargetFrameworks> | ||||
| 		<Version>2.0.7.0</Version> | ||||
| 		<Authors>Diego</Authors> | ||||
| 		<Product>ThingsGateway</Product> | ||||
| 		<Copyright>© 2023-present Diego</Copyright> | ||||
| 		<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl> | ||||
| 		<PublishRepositoryUrl>true</PublishRepositoryUrl> | ||||
| 		<EmbedUntrackedSource>true</EmbedUntrackedSource> | ||||
| 		<EmbedAllSources>true</EmbedAllSources> | ||||
| 		<RepositoryType>Gitee</RepositoryType> | ||||
| 		<IncludeSymbols>true</IncludeSymbols> | ||||
| 		<SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||||
| 		<SignAssembly>True</SignAssembly> | ||||
| 		<DelaySign>False</DelaySign> | ||||
| 		<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	 | ||||
| 	<PropertyGroup> | ||||
| 		<LangVersion>latest</LangVersion> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										101
									
								
								framework/ThingsGateway - Admin.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								framework/ThingsGateway - Admin.sln
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.6.33927.249 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{4E66C22C-0636-4949-BF6A-9E3BBE1550BA}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		admin\Directory.Build.props = admin\Directory.Build.props | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Components", "admin\ThingsGateway.Components\ThingsGateway.Components.csproj", "{0A891D8E-23B3-46AD-8D30-565EE5004F93}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "admin\ThingsGateway.Core\ThingsGateway.Core.csproj", "{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Core", "admin\ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj", "{5DA3D2BD-6768-4479-B52F-49E022EFF310}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor", "admin\ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj", "{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "admin\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{D6685A42-2712-417A-92C5-5EFF90B9FA94}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.ApiController", "admin\ThingsGateway.Admin.ApiController\ThingsGateway.Admin.ApiController.csproj", "{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "web", "web", "{F0C9A8CB-231B-45E0-B91B-4FEF7EF47197}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		web\Directory.Build.props = web\Directory.Build.props | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "web\ThingsGateway.Web.Core\ThingsGateway.Web.Core.csproj", "{D37EC028-EA46-4510-8261-6E780A906314}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Entry", "web\ThingsGateway.Web.Entry\ThingsGateway.Web.Entry.csproj", "{C5F662EB-991F-438D-BF61-EF87E7371C04}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{97B23D8B-C6C0-4746-A21F-C7B49354B284}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		..\.gitignore = ..\.gitignore | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation", "foundation\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj", "{6961511A-8787-42AF-827D-B630B2AF4791}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foundation", "foundation", "{268A1A81-2685-47E1-9986-5934A58A31A4}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{0A891D8E-23B3-46AD-8D30-565EE5004F93}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{A712EAEE-94F2-4F01-8C1C-2EC802280DD7}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{5DA3D2BD-6768-4479-B52F-49E022EFF310}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{D6685A42-2712-417A-92C5-5EFF90B9FA94}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{D37EC028-EA46-4510-8261-6E780A906314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{D37EC028-EA46-4510-8261-6E780A906314}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{D37EC028-EA46-4510-8261-6E780A906314}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{D37EC028-EA46-4510-8261-6E780A906314}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{C5F662EB-991F-438D-BF61-EF87E7371C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{C5F662EB-991F-438D-BF61-EF87E7371C04}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{C5F662EB-991F-438D-BF61-EF87E7371C04}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{C5F662EB-991F-438D-BF61-EF87E7371C04}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{6961511A-8787-42AF-827D-B630B2AF4791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{6961511A-8787-42AF-827D-B630B2AF4791}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{6961511A-8787-42AF-827D-B630B2AF4791}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{6961511A-8787-42AF-827D-B630B2AF4791}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{0A891D8E-23B3-46AD-8D30-565EE5004F93} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA} | ||||
| 		{A712EAEE-94F2-4F01-8C1C-2EC802280DD7} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA} | ||||
| 		{5DA3D2BD-6768-4479-B52F-49E022EFF310} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA} | ||||
| 		{8DD5DF98-7FDE-4B49-8661-AEB44D923CFE} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA} | ||||
| 		{D6685A42-2712-417A-92C5-5EFF90B9FA94} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA} | ||||
| 		{0D17D801-6DAA-4FD1-9A99-F9F07FA6BA88} = {4E66C22C-0636-4949-BF6A-9E3BBE1550BA} | ||||
| 		{D37EC028-EA46-4510-8261-6E780A906314} = {F0C9A8CB-231B-45E0-B91B-4FEF7EF47197} | ||||
| 		{C5F662EB-991F-438D-BF61-EF87E7371C04} = {F0C9A8CB-231B-45E0-B91B-4FEF7EF47197} | ||||
| 		{6961511A-8787-42AF-827D-B630B2AF4791} = {268A1A81-2685-47E1-9986-5934A58A31A4} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {C49B2D3E-6818-4E28-91B7-6E4E7E264BBB} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
| @@ -1,66 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DynamicApiController; | ||||
|  | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using System.ComponentModel; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application | ||||
| { | ||||
|     /// <summary> | ||||
|     /// OpenApi登录控制器 | ||||
|     /// </summary> | ||||
|     [ApiDescriptionSettings(CateGoryConst.ThingsGatewayOpenApi, Order = 200)] | ||||
|     [Route("auth/openapi")] | ||||
|     [LoggingMonitor] | ||||
|     [Description("OpenApi登录")] | ||||
|     [Authorize(AuthenticationSchemes = "Bearer")] | ||||
|     public class OpenApiAuthController : IDynamicApiController | ||||
|     { | ||||
|         private readonly IOpenApiAuthService _authService; | ||||
|         /// <summary> | ||||
|         /// <inheritdoc cref="OpenApiAuthController"/> | ||||
|         /// </summary> | ||||
|         /// <param name="authService"></param> | ||||
|         public OpenApiAuthController(IOpenApiAuthService authService) | ||||
|         { | ||||
|             _authService = authService; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// OpenApi登录 | ||||
|         /// </summary> | ||||
|         /// <param name="input"></param> | ||||
|         /// <returns></returns> | ||||
|         [AllowAnonymous] | ||||
|         [HttpPost("login")] | ||||
|         [Description(EventSubscriberConst.LoginOpenApi)] | ||||
|         public async Task<LoginOpenApiOutput> LoginOpenApiAsync(LoginOpenApiInput input) | ||||
|         { | ||||
|             return await _authService.LoginOpenApiAsync(input); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 登出 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         [HttpPost("logout")] | ||||
|         [Description(EventSubscriberConst.LogoutOpenApi)] | ||||
|         public async Task LogoutAsync() | ||||
|         { | ||||
|             await _authService.LogoutAsync(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
| using Furion.DynamicApiController; | ||||
| using Furion.SpecificationDocument; | ||||
|  | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Swagger登录授权服务 | ||||
|     /// </summary> | ||||
|     [ApiDescriptionSettings(CateGoryConst.ThingsGatewayAdmin, Order = 200)] | ||||
|     [Route("Swagger")] | ||||
|     public class SwaggerController : IDynamicApiController, IScoped | ||||
|     { | ||||
|         private readonly ConfigService _configService; | ||||
|         /// <summary> | ||||
|         /// <inheritdoc cref="SwaggerController"/> | ||||
|         /// </summary> | ||||
|         /// <param name="sysConfigService"></param> | ||||
|         public SwaggerController(ConfigService sysConfigService) | ||||
|         { | ||||
|             _configService = sysConfigService; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Swagger登录检查 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         [HttpPost("CheckUrl")] | ||||
|         [AllowAnonymous, NonUnify] | ||||
|         public async Task<int> SwaggerCheckUrlAsync() | ||||
|         { | ||||
|             var enable = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGERLOGIN_OPEN)).ConfigValue.ToBoolean(); | ||||
|             return enable ? 401 : 200; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Swagger登录 | ||||
|         /// </summary> | ||||
|         /// <param name="auth"></param> | ||||
|         /// <returns></returns> | ||||
|         [HttpPost("SubmitUrl")] | ||||
|         [AllowAnonymous, NonUnify] | ||||
|         public async Task<int> SwaggerSubmitUrlAsync([FromForm] SpecificationAuth auth) | ||||
|         { | ||||
|             var userName = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGER_NAME)).ConfigValue; | ||||
|             var password = (await _configService.GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_SWAGGER_PASSWORD)).ConfigValue; | ||||
|             if (auth.UserName == userName && auth.Password == password) | ||||
|             { | ||||
|                 return 200; | ||||
|             } | ||||
|             return 401; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,102 +0,0 @@ | ||||
| <?xml version="1.0"?> | ||||
| <doc> | ||||
|     <assembly> | ||||
|         <name>ThingsGateway.Admin.ApiController</name> | ||||
|     </assembly> | ||||
|     <members> | ||||
|         <member name="T:ThingsGateway.Admin.Application.AuthController"> | ||||
|             <summary> | ||||
|             后台登录控制器 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.AuthController.#ctor(ThingsGateway.Admin.Application.IAuthService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.Admin.Application.AuthController"/> | ||||
|             </summary> | ||||
|             <param name="authService"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.AuthController.LoginAsync(ThingsGateway.Admin.Application.LoginInput)"> | ||||
|             <summary> | ||||
|             后台登录 | ||||
|             </summary> | ||||
|             <param name="input"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.AuthController.LogoutAsync"> | ||||
|             <summary> | ||||
|             后台登出 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Application.FileController"> | ||||
|             <summary> | ||||
|             文件下载 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.FileController.#ctor(ThingsGateway.Admin.Application.IFileService,ThingsGateway.Admin.Application.IOperateLogService,ThingsGateway.Admin.Application.IVisitLogService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.Admin.Application.FileController"/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.FileController.DownloadOperateLogAsync(ThingsGateway.Admin.Application.OperateLogInput)"> | ||||
|             <summary> | ||||
|             下载操作日志 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.FileController.DownloadVisitLogAsync(ThingsGateway.Admin.Application.VisitLogInput)"> | ||||
|             <summary> | ||||
|             下载访问日志 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Application.OpenApiAuthController"> | ||||
|             <summary> | ||||
|             OpenApi登录控制器 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.#ctor(ThingsGateway.Admin.Application.IOpenApiAuthService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.Admin.Application.OpenApiAuthController"/> | ||||
|             </summary> | ||||
|             <param name="authService"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.LoginOpenApiAsync(ThingsGateway.Admin.Application.LoginOpenApiInput)"> | ||||
|             <summary> | ||||
|             OpenApi登录 | ||||
|             </summary> | ||||
|             <param name="input"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.OpenApiAuthController.LogoutAsync"> | ||||
|             <summary> | ||||
|             登出 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Application.SwaggerController"> | ||||
|             <summary> | ||||
|             Swagger登录授权服务 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.SwaggerController.#ctor(ThingsGateway.Admin.Application.ConfigService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.Admin.Application.SwaggerController"/> | ||||
|             </summary> | ||||
|             <param name="sysConfigService"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.SwaggerController.SwaggerCheckUrlAsync"> | ||||
|             <summary> | ||||
|             Swagger登录检查 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Application.SwaggerController.SwaggerSubmitUrlAsync(Furion.SpecificationDocument.SpecificationAuth)"> | ||||
|             <summary> | ||||
|             Swagger登录 | ||||
|             </summary> | ||||
|             <param name="auth"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
| @@ -1,104 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": "22222222222222", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_SWAGGER_NAME", | ||||
|       "ConfigValue": "admin", | ||||
|       "Remark": "swagger账号", | ||||
|       "SortCode": "1", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222223", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_SWAGGER_PASSWORD", | ||||
|       "ConfigValue": "123456", | ||||
|       "Remark": "swagger密码", | ||||
|       "SortCode": "2", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222224", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_SWAGGERLOGIN_OPEN", | ||||
|       "ConfigValue": "false", | ||||
|       "Remark": "swagger开启登录", | ||||
|       "SortCode": "3", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|  | ||||
|     { | ||||
|       "Id": "22222222222226", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_TITLE", | ||||
|       "ConfigValue": "ThingsGateway", | ||||
|       "Remark": "标题", | ||||
|       "SortCode": "5", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222228", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_COPYRIGHT", | ||||
|       "ConfigValue": "ThingsGateway ©2023 Diego", | ||||
|       "Remark": "系统版权", | ||||
|       "SortCode": "6", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222229", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_COPYRIGHT_URL", | ||||
|       "ConfigValue": "https://gitee.com/diego2098/ThingsGateway", | ||||
|       "Remark": "系统版权链接地址", | ||||
|       "SortCode": "7", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222231", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_PASSWORD", | ||||
|       "ConfigValue": "111111", | ||||
|       "Remark": "默认用户密码", | ||||
|       "SortCode": "8", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222227", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_VERIFICAT_EXPIRES", | ||||
|       "ConfigValue": "14400", | ||||
|       "Remark": "Verificat过期时间(分)", | ||||
|       "SortCode": "9", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222232", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_SINGLE_OPEN", | ||||
|       "ConfigValue": "false", | ||||
|       "Remark": "单用户登录开关", | ||||
|       "SortCode": "10", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222230", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_CAPTCHA_OPEN", | ||||
|       "ConfigValue": "true", | ||||
|       "Remark": "登录验证码开关", | ||||
|       "SortCode": "11", | ||||
|       "IsDelete": "false" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "22222222222225", | ||||
|       "Category": "SYS_CONFIGBASEDEFAULT", | ||||
|       "ConfigKey": "CONFIG_REMARK", | ||||
|       "ConfigValue": "边缘采集网关", | ||||
|       "Remark": "说明", | ||||
|       "SortCode": "12", | ||||
|       "IsDelete": "false" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": "212725263001001", | ||||
|       "Code": "superAdmin", | ||||
|       "Name": "超级管理员", | ||||
|       "SortCode": "1" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "212725263001002", | ||||
|       "Code": "admin", | ||||
|       "Name": "业务管理员", | ||||
|       "SortCode": "2" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,34 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": "212725263002001", | ||||
|       "Account": "superAdmin", | ||||
|       "LastLoginDevice": "PC", | ||||
|       "LastLoginIp": "0.0.0.1", | ||||
|       "LastLoginTime": "2023-03-03 21:18:43.7092169", | ||||
|       "LatestLoginDevice": "PC", | ||||
|       "LatestLoginIp": "0.0.0.1", | ||||
|       "LatestLoginTime": "2023-03-03 21:19:16.1043309", | ||||
|       "Password": "7DA385A25A98388E", | ||||
|       "SortCode": "1", | ||||
|       "UserEnable": "true", | ||||
|       "IsDelete": "false", | ||||
|       "UpdateTime": "2023-03-03 21:19:16.1202211" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "201725263002001", | ||||
|       "Account": "admin", | ||||
|       "LastLoginDevice": "PC", | ||||
|       "LastLoginIp": "0.0.0.1", | ||||
|       "LastLoginTime": "2023-03-03 18:20:49.1875384", | ||||
|       "LatestLoginDevice": "PC", | ||||
|       "LatestLoginIp": "0.0.0.1", | ||||
|       "LatestLoginTime": "2023-03-03 18:23:08.6424099", | ||||
|       "Password": "7DA385A25A98388E", | ||||
|       "SortCode": "2", | ||||
|       "UserEnable": "true", | ||||
|       "IsDelete": "false", | ||||
|       "UpdateTime": "2023-03-03 18:23:08.6727296" | ||||
|     } | ||||
|     ] | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <Content Include="SeedData\Json\sys_config.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\sys_relation.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\sys_resource.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\sys_role.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\sys_user.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| </Project> | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,39 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// AppBarItems | ||||
| /// </summary> | ||||
| public partial class AppBarItems | ||||
| { | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_COPYRIGHT_URL { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <20><>Ȩ | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_COPYRIGHT { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_TITLE { get; set; } | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using BlazorComponent; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using Microsoft.AspNetCore.Components.Routing; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
| /// <summary> | ||||
| /// Breadcrumb | ||||
| /// </summary> | ||||
| public partial class Breadcrumb | ||||
| { | ||||
|     private List<BreadcrumbItem> BreadcrumbItems = new(); | ||||
|  | ||||
|     [Inject] | ||||
|     UserResoures UserResoures { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     public override void Dispose() | ||||
|     { | ||||
|         NavigationManager.LocationChanged -= OnLocationChanged; | ||||
|         base.Dispose(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         NavigationManager.LocationChanged += OnLocationChanged; | ||||
|         BreadcrumbItems = GetBreadcrumbItems(); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|     private List<BreadcrumbItem> GetBreadcrumbItems() | ||||
|     { | ||||
|         var items = new List<BreadcrumbItem>(); | ||||
|         var currentNav = UserResoures.AllSameLevelMenuSpas.FirstOrDefault(n => n.Component is not null && NavigationManager.Uri.Replace(NavigationManager.BaseUri, "/") == (n.Component)); | ||||
|         if (currentNav is not null) | ||||
|         { | ||||
|             if (currentNav.ParentId != 0) | ||||
|             { | ||||
|                 var parentNav = UserResoures.AllSameLevelMenuSpas.FirstOrDefault(n => n.Id == currentNav.ParentId); | ||||
|                 if (parentNav != null) | ||||
|                     items.Add(new BreadcrumbItem { Text = parentNav.Title, Href = null }); | ||||
|             } | ||||
|  | ||||
|             items.Add(new BreadcrumbItem() { Text = currentNav.Title, Href = currentNav.Component }); | ||||
|             items.Last().Href = currentNav.Component; | ||||
|         } | ||||
|  | ||||
|         return items; | ||||
|     } | ||||
|  | ||||
|     private void OnLocationChanged(object sender, LocationChangedEventArgs e) | ||||
|     { | ||||
|         BreadcrumbItems = GetBreadcrumbItems(); | ||||
|         InvokeAsync(StateHasChanged); | ||||
|     } | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @inject NavigationManager NavigationManager | ||||
| @namespace ThingsGateway.Admin.Blazor.Core | ||||
| @inject UserResoures UserResoures; | ||||
| @inherits BaseComponentBase | ||||
| @using BlazorComponent; | ||||
| @using Masa.Blazor; | ||||
|  | ||||
|  | ||||
| <div class="ml-16"> | ||||
|     <MMenu OffsetY Bottom Right CloseOnContentClick="true" @bind-Value="_open" MinWidth="@("auto")"> | ||||
|         <ActivatorContent> | ||||
|             <MTooltip Color="primary" Bottom> | ||||
|                 <ActivatorContent Context="tooltipContext"> | ||||
|                     @{ | ||||
|                         var attrs = new Dictionary<string, object>(); | ||||
|                         foreach (var (key, value) in context.Attrs) | ||||
|                         { | ||||
|                             attrs.Add(key, value); | ||||
|                         } | ||||
|                         foreach (var (key, value) in tooltipContext.Attrs) | ||||
|                         { | ||||
|                             if (attrs.ContainsKey(key) is false) attrs.Add(key, value); | ||||
|                         } | ||||
|                     } | ||||
|                     <MIcon @attributes="@attrs" Size=20 Color="dark-yellow">mdi-star-outline</MIcon> | ||||
|                 </ActivatorContent> | ||||
|                 <ChildContent> | ||||
|                     <span>收藏</span> | ||||
|                 </ChildContent> | ||||
|             </MTooltip> | ||||
|         </ActivatorContent> | ||||
|         <ChildContent> | ||||
|             <MList Class="pb-1" Style="min-width:320px;"> | ||||
|                 @foreach (var nav in UserResoures.WorkbenchOutputs) | ||||
|                 { | ||||
|                     <MListItem Dense OnClick="()=> NavigationManager.NavigateTo(nav.Component)" Class="px-4"> | ||||
|                         <MListItemAction Class="mr-3"> | ||||
|                             <MIcon Size=20 Color="neutral-lighten-3">@nav.Icon</MIcon> | ||||
|                         </MListItemAction> | ||||
|                         <MListItemContent> | ||||
|                             <span Class="text-btn">@(nav.Title)</span> | ||||
|                         </MListItemContent> | ||||
|                     </MListItem> | ||||
|                 } | ||||
|             </MList> | ||||
|         </ChildContent> | ||||
|     </MMenu> | ||||
| </div> | ||||
| @@ -1,21 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
| /// <summary> | ||||
| /// <20>ղ<EFBFBD>/<2F><><EFBFBD>ݷ<EFBFBD>ʽ | ||||
| /// </summary> | ||||
| public partial class Favorite | ||||
| { | ||||
|     bool _open; | ||||
|  | ||||
| } | ||||
| @@ -1,47 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
| /// <summary> | ||||
| /// Foter | ||||
| /// </summary> | ||||
| public partial class Foter | ||||
| { | ||||
|     private string Version = ""; | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_COPYRIGHT_URL { get; set; } | ||||
|     /// <summary> | ||||
|     /// <20><>Ȩ | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_COPYRIGHT { get; set; } | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_TITLE { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         Version = "v" + Assembly.GetExecutingAssembly().GetName().Version.ToString(); | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,45 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// Logo | ||||
| /// </summary> | ||||
| public partial class Logo | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Logo<67>߶<EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public int HeightInt { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_COPYRIGHT_URL { get; set; } | ||||
|     /// <summary> | ||||
|     /// <20><>Ȩ | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_COPYRIGHT { get; set; } | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string CONFIG_TITLE { get; set; } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -1,53 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Masa.Blazor.Presets; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
| /// <summary> | ||||
| /// PageTabs | ||||
| /// </summary> | ||||
| public partial class PageTabs | ||||
| { | ||||
|     private TabOptions TabOptions(PageTabPathValue value) | ||||
|     { | ||||
|         var item = UserResoures.PageTabItems.FirstOrDefault(u => value.IsMatch(u.Href)); | ||||
|         var title = item?.Title; | ||||
|         var icon = item?.Icon; | ||||
|         var titleClass = $"mx-2 text-capitalize {(value.Selected ? "primary--text" : "")}"; | ||||
|         var op = new TabOptions(title, icon, titleClass) | ||||
|         { | ||||
|             TitleStyle = "min-width:46px;", | ||||
|             Class = "systemTab", | ||||
|         }; | ||||
|         return op; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// Tabsʵ<73><CAB5> | ||||
|     /// </summary> | ||||
|     public PPageTabs PPageTabs { get; private set; } | ||||
|  | ||||
|     [Inject] | ||||
|     UserResoures UserResoures { get; set; } | ||||
|     /// <summary> | ||||
|     /// SelfPatterns | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public IEnumerable<string> SelfPatterns { get; set; } | ||||
|     /// <summary> | ||||
|     /// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public RenderFragment ChildContent { get; set; } | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
| /// <summary> | ||||
| /// Search | ||||
| /// </summary> | ||||
| public partial class Search | ||||
| { | ||||
|     private string _value; | ||||
|     private string Value | ||||
|     { | ||||
|         get => _value; | ||||
|         set | ||||
|         { | ||||
|             _value = value; | ||||
|             if (!string.IsNullOrEmpty(value)) | ||||
|             { | ||||
|                 NavigationManager.NavigateTo(value); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Inject] | ||||
|     private NavigationManager NavigationManager { get; set; } | ||||
|  | ||||
|     private List<SysResource> AvalidMenus; | ||||
|     [Inject] | ||||
|     private UserResoures UserResoures { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void OnParametersSet() | ||||
|     { | ||||
|         AvalidMenus = UserResoures.SameLevelMenus.Where(it => it.Component != null).ToList(); | ||||
|         base.OnParametersSet(); | ||||
|     } | ||||
| } | ||||
| @@ -1,61 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using BlazorComponent; | ||||
|  | ||||
| using Masa.Blazor; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
| /// <summary> | ||||
| /// UserMenu | ||||
| /// </summary> | ||||
| public partial class UserMenu | ||||
| { | ||||
|     [Inject] | ||||
|     NavigationManager NavigationManager { get; set; } | ||||
|     [Inject] | ||||
|     private UserResoures UserResoures { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     private AjaxService AjaxService { get; set; } | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private async Task LogoutAsync() | ||||
|     { | ||||
|         var ajaxOption = new AjaxOption | ||||
|         { | ||||
|             Url = "/auth/b/logout", | ||||
|         }; | ||||
|         var str = await AjaxService.GetMessageAsync(ajaxOption); | ||||
|         var ret = str?.ToJsonWithT<UnifyResult<string>>(); | ||||
|         if (ret?.Code != 200) | ||||
|         { | ||||
|             await PopupService.EnqueueSnackbarAsync("ע<><D7A2>ʧ<EFBFBD><CAA7>", AlertTypes.Error); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             await PopupService.EnqueueSnackbarAsync("ע<><D7A2><EFBFBD>ɹ<EFBFBD>", AlertTypes.Success); | ||||
|             await Task.Delay(500); | ||||
|             NavigationManager.NavigateTo(NavigationManager.Uri); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
|  | ||||
| using Masa.Blazor; | ||||
| using Masa.Blazor.Presets; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// AppStartup启动类 | ||||
| /// </summary> | ||||
| public class Startup : AppStartup | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public void ConfigureServices(IServiceCollection services) | ||||
|     { | ||||
|         services.AddMasaBlazor(options => | ||||
|         { | ||||
|             options.Defaults = new Dictionary<string, IDictionary<string, object>>() | ||||
|             { | ||||
|                 { | ||||
|                     PopupComponents.SNACKBAR, new Dictionary<string, object>() | ||||
|                     { | ||||
|                         { nameof(PEnqueuedSnackbars.Closeable), true }, | ||||
|                         { nameof(PEnqueuedSnackbars.Position), SnackPosition.TopCenter } | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|             options.ConfigureTheme(theme => | ||||
|             { | ||||
|                 theme.Themes.Dark.Accent = "#FF4081"; | ||||
|                 theme.Themes.Dark.Error = "#FF5252"; | ||||
|                 theme.Themes.Dark.Info = "#2196F3"; | ||||
|                 theme.Themes.Dark.Primary = "#2196F3"; | ||||
|                 theme.Themes.Dark.Secondary = "#424242"; | ||||
|                 theme.Themes.Dark.Success = "#4CAF50"; | ||||
|                 theme.Themes.Dark.Warning = "#FB8C00"; | ||||
|                 theme.Themes.Dark.UserDefined.Add("barcolor", "#1e1e1e"); | ||||
|  | ||||
|                 theme.Themes.Light.Accent = "#82B1FF"; | ||||
|                 theme.Themes.Light.Error = "#FF5252"; | ||||
|                 theme.Themes.Light.Info = "#2196F3"; | ||||
|                 theme.Themes.Light.Primary = "#1976D2"; | ||||
|                 theme.Themes.Light.Secondary = "#424242"; | ||||
|                 theme.Themes.Light.Success = "#4CAF50"; | ||||
|                 theme.Themes.Light.Warning = "#FB8C00"; | ||||
|                 theme.Themes.Light.UserDefined.Add("barcolor", "#fff"); | ||||
|  | ||||
|  | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         services.AddScoped<InitTimezone>(); | ||||
|         services.AddScoped<AjaxService>(); | ||||
|         services.AddScoped<UserResoures>(); | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
|  | ||||
| 		<PackageReference Include="Masa.Blazor" Version="1.0.1" /> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.10" /> | ||||
|  | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 	  <Content Update="wwwroot\**"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| @@ -1,861 +0,0 @@ | ||||
| <?xml version="1.0"?> | ||||
| <doc> | ||||
|     <assembly> | ||||
|         <name>ThingsGateway.Admin.Blazor.Core</name> | ||||
|     </assembly> | ||||
|     <members> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.BaseComponentBase"> | ||||
|             <summary> | ||||
|             Razor组件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.PopupService"> | ||||
|             <summary> | ||||
|             弹出层服务 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.Dispose"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.InvokeStateHasChangedAsync"> | ||||
|             <summary> | ||||
|             InvokeAsync(StateHasChanged) | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.IsMobile"> | ||||
|             <summary> | ||||
|             是否手机端 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.BaseComponentBase.Changed"> | ||||
|             <summary> | ||||
|             主动更新 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AppBarItems"> | ||||
|             <summary> | ||||
|             AppBarItems | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppBarItems.CONFIG_COPYRIGHT_URL"> | ||||
|             <summary> | ||||
|             链接 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppBarItems.CONFIG_COPYRIGHT"> | ||||
|             <summary> | ||||
|             版权 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppBarItems.CONFIG_TITLE"> | ||||
|             <summary> | ||||
|             标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Filters"> | ||||
|             <summary> | ||||
|             过滤选择Model | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Filters.Key"> | ||||
|             <summary> | ||||
|             DateTable Value | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Filters.Title"> | ||||
|             <summary> | ||||
|             DateTable Text | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Filters.Value"> | ||||
|             <summary> | ||||
|             是否显示 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.PageSize"> | ||||
|             <summary> | ||||
|             分页选择Model | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.PageSize.Key"> | ||||
|             <summary> | ||||
|             显示 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.PageSize.Value"> | ||||
|             <summary> | ||||
|             值 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker"> | ||||
|             <summary> | ||||
|             DateTimePicker | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.Max"> | ||||
|             <summary> | ||||
|             max time  [utc] | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.Min"> | ||||
|             <summary> | ||||
|             min time  [utc] | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.NoTitle"> | ||||
|             <summary> | ||||
|             NoTitle | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.Value"> | ||||
|             <summary> | ||||
|             selected datetime[utc] | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.ValueChanged"> | ||||
|             <summary> | ||||
|             ValueChanged | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.ChildContent"> | ||||
|             <summary> | ||||
|             ChildContent | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.OutputTimezoneOffset"> | ||||
|             <summary> | ||||
|             OutputTimezoneOffset | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.DisplayTimezoneOffset"> | ||||
|             <summary> | ||||
|             DisplayTimezoneOffset | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView)"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|             <param name="parameters"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AppDateTimePicker.UpdateValueAsync(System.Nullable{System.DateTime})"> | ||||
|             <summary> | ||||
|              | ||||
|             </summary> | ||||
|             <param name="dateTime">accept the time using display time zone</param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AppListGroup`1"> | ||||
|             <summary> | ||||
|             AppListGroup | ||||
|             </summary> | ||||
|             <typeparam name="TItem"></typeparam> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.Icon"> | ||||
|             <summary> | ||||
|             icon | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.Item"> | ||||
|             <summary> | ||||
|             item | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.SubGroup"> | ||||
|             <summary> | ||||
|             sub | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AppListGroup`1.OnInitialized"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Breadcrumb"> | ||||
|             <summary> | ||||
|             Breadcrumb | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Breadcrumb.Dispose"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Breadcrumb.OnInitialized"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.EnableChip"> | ||||
|             <summary> | ||||
|             启用/停用 文本提示 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.Class"> | ||||
|             <summary> | ||||
|             Class | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.Style"> | ||||
|             <summary> | ||||
|             Style | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.Value"> | ||||
|             <summary> | ||||
|             Value | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.DisabledLabel"> | ||||
|             <summary> | ||||
|             DisabledLabel | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.EnableChip.EnabledLabel"> | ||||
|             <summary> | ||||
|             EnabledLabel | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Favorite"> | ||||
|             <summary> | ||||
|             收藏/快捷方式 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Foter"> | ||||
|             <summary> | ||||
|             Foter | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Foter.CONFIG_COPYRIGHT_URL"> | ||||
|             <summary> | ||||
|             链接 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Foter.CONFIG_COPYRIGHT"> | ||||
|             <summary> | ||||
|             版权 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Foter.CONFIG_TITLE"> | ||||
|             <summary> | ||||
|             标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Foter.OnParametersSetAsync"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Logo"> | ||||
|             <summary> | ||||
|             Logo | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Logo.HeightInt"> | ||||
|             <summary> | ||||
|             Logo高度 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Logo.CONFIG_COPYRIGHT_URL"> | ||||
|             <summary> | ||||
|             链接 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Logo.CONFIG_COPYRIGHT"> | ||||
|             <summary> | ||||
|             版权 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Logo.CONFIG_TITLE"> | ||||
|             <summary> | ||||
|             标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AppItem"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Children"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Divider"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.HasChildren"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Heading"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Href"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Icon"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.SubTitle"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Target"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Title"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppItem.Value"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.IAppItem`1"> | ||||
|             <summary> | ||||
|             ListItem | ||||
|             </summary> | ||||
|             <typeparam name="TItem"></typeparam> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Children"> | ||||
|             <summary> | ||||
|             子菜单 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Divider"> | ||||
|             <summary> | ||||
|             是否启用下划线 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Heading"> | ||||
|             <summary> | ||||
|             菜单头部标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Href"> | ||||
|             <summary> | ||||
|             链接 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Icon"> | ||||
|             <summary> | ||||
|             图标 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.SubTitle"> | ||||
|             <summary> | ||||
|             菜单副标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Target"> | ||||
|             <summary> | ||||
|             跳转方式 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Title"> | ||||
|             <summary> | ||||
|             菜单标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.IAppItem`1.Value"> | ||||
|             <summary> | ||||
|             菜单值 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.IAppItem`1.HasChildren"> | ||||
|             <summary> | ||||
|             是否有子菜单 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.NavItem"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.NavItem.#ctor"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Children"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Divider"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Group"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Heading"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Href"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Icon"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Segment"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.State"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.SubTitle"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Target"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Title"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.NavItem.Value"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.PageTabs"> | ||||
|             <summary> | ||||
|             PageTabs | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.PageTabs.PPageTabs"> | ||||
|             <summary> | ||||
|             Tabs实例 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.PageTabs.SelfPatterns"> | ||||
|             <summary> | ||||
|             SelfPatterns | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.PageTabs.ChildContent"> | ||||
|             <summary> | ||||
|             子组件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Search"> | ||||
|             <summary> | ||||
|             Search | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Search.OnParametersSet"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.SysSignalR"> | ||||
|             <summary> | ||||
|             SignalR连接 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.SysSignalR.DisposeAsync"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.SysSignalR.OnAfterRenderAsync(System.Boolean)"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.UserMenu"> | ||||
|             <summary> | ||||
|             UserMenu | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserMenu.OnInitialized"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst"> | ||||
|             <summary> | ||||
|             资源标识常量 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.ResourceUrl"> | ||||
|             <summary> | ||||
|             资源默认路径 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.DataTableActions"> | ||||
|             <summary> | ||||
|             表格操作列标识 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.ThemeCookieKey"> | ||||
|             <summary> | ||||
|             主题Cookie | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.AppBarHeight"> | ||||
|             <summary> | ||||
|             AppBarHeight | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.PageTabsHeight"> | ||||
|             <summary> | ||||
|             Tab高度 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.FooterHeight"> | ||||
|             <summary> | ||||
|             FooterHeight | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="F:ThingsGateway.Admin.Blazor.Core.BlazorResourceConst.DefaultHeight"> | ||||
|             <summary> | ||||
|             DefaultHeight | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.MenuExtensions"> | ||||
|             <summary> | ||||
|             菜单扩展 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.MenuExtensions.Parse(System.Collections.Generic.List{ThingsGateway.Admin.Core.SysResource})"> | ||||
|             <summary> | ||||
|             转化为NavItem | ||||
|             </summary> | ||||
|             <param name="menus"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions"> | ||||
|             <summary> | ||||
|             扩展方法 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions.OpenConfirmDialogAsync(Masa.Blazor.IPopupService,System.String,System.String)"> | ||||
|             <summary> | ||||
|             确认弹窗,默认Error | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions.OpenConfirmDialogAsync(Masa.Blazor.IPopupService,System.String,System.String,BlazorComponent.AlertTypes)"> | ||||
|             <summary> | ||||
|             确认弹窗 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.PopupServiceExtensions.OpenInformationMessageAsync(Masa.Blazor.IPopupService,System.String)"> | ||||
|             <summary> | ||||
|             消息提示 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Ajax"> | ||||
|             <summary> | ||||
|             Ajax组件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.Ajax.JSRuntime"> | ||||
|             <summary> | ||||
|             获得/设置 IJSRuntime 实例 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.Dispose"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.GetMessageAsync(ThingsGateway.Admin.Blazor.Core.AjaxOption)"> | ||||
|             <summary> | ||||
|             请求并返回消息 | ||||
|             </summary> | ||||
|             <param name="option">Ajax配置</param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.OnAfterRenderAsync(System.Boolean)"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Ajax.OnInitialized"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AjaxOption"> | ||||
|             <summary> | ||||
|             Ajax配置类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AjaxOption.Data"> | ||||
|             <summary> | ||||
|             获取/设置 要上传的参数类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AjaxOption.Method"> | ||||
|             <summary> | ||||
|             获取/设置 传输方式,默认为POST | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AjaxOption.Url"> | ||||
|             <summary> | ||||
|             获取/设置 请求的URL | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AjaxService"> | ||||
|             <summary> | ||||
|             Ajax服务类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AjaxService.Cache"> | ||||
|             <summary> | ||||
|             获得 回调委托缓存集合 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AjaxService.GotoCache"> | ||||
|             <summary> | ||||
|             获得 跳转其他页面的回调委托缓存集合 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AjaxService.DownFileCache"> | ||||
|             <summary> | ||||
|             获得 下载委托缓存集合 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.GetMessageAsync(ThingsGateway.Admin.Blazor.Core.AjaxOption)"> | ||||
|             <summary> | ||||
|             调用Ajax方法发送请求 | ||||
|             </summary> | ||||
|             <param name="option"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.DownFileAsync(System.String,System.String,System.Object)"> | ||||
|             <summary> | ||||
|             调用Ajax方法发送请求 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.GotoAsync(System.String)"> | ||||
|             <summary> | ||||
|             调用 Goto 方法跳转其他页面 | ||||
|             </summary> | ||||
|             <param name="url"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.Register(Microsoft.AspNetCore.Components.IComponent,System.Func{ThingsGateway.Admin.Blazor.Core.AjaxOption,System.Threading.Tasks.Task{System.String}})"> | ||||
|             <summary> | ||||
|             注册服务 | ||||
|             </summary> | ||||
|             <param name="key"></param> | ||||
|             <param name="callback"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.RegisterGoto(Microsoft.AspNetCore.Components.IComponent,System.Func{System.String,System.Threading.Tasks.Task})"> | ||||
|             <summary> | ||||
|             注册服务 | ||||
|             </summary> | ||||
|             <param name="key"></param> | ||||
|             <param name="callback"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.RegisterDownFile(Microsoft.AspNetCore.Components.IComponent,System.Func{System.String,System.String,System.Object,System.Threading.Tasks.Task})"> | ||||
|             <summary> | ||||
|             注册服务 | ||||
|             </summary> | ||||
|             <param name="key"></param> | ||||
|             <param name="callback"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.UnRegister(Microsoft.AspNetCore.Components.IComponent)"> | ||||
|             <summary> | ||||
|             注销事件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.UnRegisterGoto(Microsoft.AspNetCore.Components.IComponent)"> | ||||
|             <summary> | ||||
|             注销事件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AjaxService.UnRegisterDownFile(Microsoft.AspNetCore.Components.IComponent)"> | ||||
|             <summary> | ||||
|             注销事件 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent"> | ||||
|             <summary> | ||||
|             动态组件类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.#ctor(System.Type,System.Collections.Generic.IDictionary{System.String,System.Object})"> | ||||
|             <summary> | ||||
|             构造函数 | ||||
|             </summary> | ||||
|             <param name="componentType"></param> | ||||
|             <param name="parameters">TCom 组件所需要的参数集合</param> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.ComponentType"> | ||||
|             <summary> | ||||
|             获得/设置 组件类型 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.Parameters"> | ||||
|             <summary> | ||||
|             获得/设置 组件参数集合 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.CreateComponent``1(System.Collections.Generic.IDictionary{System.String,System.Object})"> | ||||
|             <summary> | ||||
|             创建自定义组件方法 | ||||
|             </summary> | ||||
|             <typeparam name="TCom"></typeparam> | ||||
|             <param name="parameters">TCom 组件所需要的参数集合</param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.CreateComponent``1"> | ||||
|             <summary> | ||||
|             创建自定义组件方法 | ||||
|             </summary> | ||||
|             <typeparam name="TCom"></typeparam> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.BootstrapDynamicComponent.Render(System.Action{System.Object})"> | ||||
|             <summary> | ||||
|             创建组件实例并渲染 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.JSModuleExtensions"> | ||||
|             <summary> | ||||
|             JSModule extensions class | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.JSModuleExtensions.LoadModuleAsync(Microsoft.JSInterop.IJSRuntime,System.String,System.Boolean)"> | ||||
|             <summary> | ||||
|             IJSRuntime 扩展方法 动态加载脚本 | ||||
|             </summary> | ||||
|             <param name="jsRuntime"></param> | ||||
|             <param name="fileName"></param> | ||||
|             <param name="relative">是否为相对路径 默认 true</param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.JSRuntimeExtensions"> | ||||
|             <summary> | ||||
|             JSRuntime 扩展操作类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.JSRuntimeExtensions.InternalInvokeAsync``1(Microsoft.JSInterop.IJSRuntime,System.String,System.Threading.CancellationToken,System.Object[])"> | ||||
|             <summary> | ||||
|             调用 JSInvoke 方法 | ||||
|             </summary> | ||||
|             <param name="jsRuntime">IJSRuntime 实例</param> | ||||
|             <param name="func">Javascript 方法</param> | ||||
|             <param name="token">取消传播</param> | ||||
|             <param name="args">Javascript 参数</param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.JSRuntimeExtensions.InternalInvokeVoidAsync(Microsoft.JSInterop.IJSRuntime,System.String,System.Threading.CancellationToken,System.Object[])"> | ||||
|             <summary> | ||||
|             调用 JSInvoke 方法 | ||||
|             </summary> | ||||
|             <param name="jsRuntime">IJSRuntime 实例</param> | ||||
|             <param name="func">Javascript 方法</param> | ||||
|             <param name="token">取消传播</param> | ||||
|             <param name="args">Javascript 参数</param> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.InitTimezone"> | ||||
|             <summary> | ||||
|             获取Web客户端时差 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.InitTimezone.TimezoneOffset"> | ||||
|             <summary> | ||||
|             当前的客户端时差 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.InitTimezone.#ctor(Microsoft.JSInterop.IJSRuntime,BlazorComponent.I18n.CookieStorage,Microsoft.AspNetCore.Http.IHttpContextAccessor)"> | ||||
|             <summary> | ||||
|             构造函数 | ||||
|             </summary> | ||||
|             <param name="jsRuntime"></param> | ||||
|             <param name="storage"></param> | ||||
|             <param name="httpContextAccessor"></param> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.InitTimezone.SetTimezoneOffsetAsync"> | ||||
|             <summary> | ||||
|             获取Web客户端时差 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.InitTimezone.Dispose"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.Startup"> | ||||
|             <summary> | ||||
|             AppStartup启动类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.Startup.ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.UserResoures"> | ||||
|             <summary> | ||||
|             当前用户资源 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.#ctor(BlazorComponent.I18n.CookieStorage,Masa.Blazor.MasaBlazor,Microsoft.AspNetCore.Http.IHttpContextAccessor)"> | ||||
|             <summary> | ||||
|             构造函数 | ||||
|             </summary> | ||||
|             <param name="cookieStorage"></param> | ||||
|             <param name="masaBlazor"></param> | ||||
|             <param name="httpContextAccessor"></param> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.CurrentUser"> | ||||
|             <summary> | ||||
|             当前用户 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.IsDark"> | ||||
|             <summary> | ||||
|             是否深色主图 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.Menus"> | ||||
|             <summary> | ||||
|             当前菜单 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.PageTabItems"> | ||||
|             <summary> | ||||
|             当前的Tab列表 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.SameLevelMenus"> | ||||
|             <summary> | ||||
|             当前的菜单列表 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.AllSameLevelMenuSpas"> | ||||
|             <summary> | ||||
|             当前的菜单与单页列表 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.UserResoures.WorkbenchOutputs"> | ||||
|             <summary> | ||||
|             当前工作台 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.Dispose"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.InitAllAsync"> | ||||
|             <summary> | ||||
|             初始化获取全部资源 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.InitMenuAsync"> | ||||
|             <summary> | ||||
|             初始化获取当前菜单资源 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.InitUserAsync"> | ||||
|             <summary> | ||||
|             初始化获取当前用户 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.IsHasButtonWithRole(System.String)"> | ||||
|             <summary> | ||||
|             是否拥有按钮授权 | ||||
|             </summary> | ||||
|             <param name="code"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.IsHasPageWithRole(System.String)"> | ||||
|             <summary> | ||||
|             是否拥有页面授权 | ||||
|             </summary> | ||||
|             <param name="code"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.UserResoures.SetMasaTheme(System.Nullable{System.Boolean})"> | ||||
|             <summary> | ||||
|             设置深浅主题统一由这个方法为入口 | ||||
|             </summary> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
| @@ -1,84 +0,0 @@ | ||||
| /*自定义样式*/ | ||||
| .table-text-truncate { | ||||
|     white-space: nowrap !important; | ||||
|     overflow: hidden !important; | ||||
|     text-overflow: ellipsis !important; | ||||
|     max-width: 200px; | ||||
|     min-width: 130px; | ||||
| } | ||||
|  | ||||
| .table-minwidth { | ||||
|     min-width: 130px; | ||||
| } | ||||
|  | ||||
| /*masa样式覆盖修改*/ | ||||
| .m-application--is-ltr .m-data-table__mobile-row__cell { | ||||
|     text-align: right; | ||||
|     white-space: nowrap !important; | ||||
|     overflow: hidden !important; | ||||
|     text-overflow: ellipsis !important; | ||||
|     max-width: 200px; | ||||
| } | ||||
|  | ||||
| .m-application--is-ltr .m-list-group--no-action > .m-list-group__items > .m-list-item { | ||||
|     padding-left: 36px; | ||||
| } | ||||
|  | ||||
| .m-application--is-ltr .m-list--dense.m-list--nav .m-list-group--no-action > .m-list-group__items > .m-list-item { | ||||
|     padding-left: 36px; | ||||
| } | ||||
|  | ||||
| .m-application--is-ltr .m-list-item__action:first-child, .m-application--is-ltr .m-list-item__icon:first-child { | ||||
|     margin-right: 8px; | ||||
| } | ||||
|  | ||||
| .m-tabs-bar { | ||||
|     height: auto; | ||||
| } | ||||
|  | ||||
| .m-breadcrumbs li:nth-child(even) { | ||||
|     padding: 0 6px; | ||||
| } | ||||
|  | ||||
|  | ||||
| .m-page-tabs > .m-tabs-bar { | ||||
|     border-bottom-left-radius: 20px !important; | ||||
|     border-bottom-right-radius: 20px !important; | ||||
|     height: 36px; | ||||
|     padding: 0 16px; | ||||
| } | ||||
|  | ||||
|  | ||||
| .m-application .text-start { | ||||
|     text-align: start !important; | ||||
|     min-width: 130px; | ||||
| } | ||||
| .neutral--text { | ||||
|     color: #1B2559 !important; | ||||
|     caret-color: #1B2559 !important; | ||||
| } | ||||
|  | ||||
|  | ||||
| /*下面都是html默认样式修改*/ | ||||
| html { | ||||
|     overflow-y: hidden | ||||
| } | ||||
|  | ||||
| /*滚动条样式*/ | ||||
| ::-webkit-scrollbar-track { | ||||
|     border-radius: 10px; | ||||
|     margin: 12px 0 0 0; | ||||
|     background-color: #F6F8FD; | ||||
| } | ||||
|  | ||||
| ::-webkit-scrollbar { | ||||
|     width: 4px; | ||||
|     height: 6px; | ||||
|     margin: 5px 0; | ||||
| } | ||||
|  | ||||
| ::-webkit-scrollbar-thumb { | ||||
|     margin: 5px 0; | ||||
|     border-radius: 4px; | ||||
|     background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.44, #A3AED0), color-stop(0.72, #A3AED0), color-stop(0.86, #A3AED0)); | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,53 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Base | ||||
|     /// </summary> | ||||
|     public partial class Base | ||||
|     { | ||||
|  | ||||
|         /// <summary> | ||||
|         /// <inheritdoc/> | ||||
|         /// </summary> | ||||
|         /// <param name="firstRender"></param> | ||||
|         /// <returns></returns> | ||||
|         protected override async Task OnAfterRenderAsync(bool firstRender) | ||||
|         { | ||||
|             if (firstRender) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     var data = await App.GetService<UserCenterService>().GetLoginDefaultRazorAsync(UserManager.UserId); | ||||
|                     var sameLevelMenus = await App.GetService<IResourceService>().GetaMenuAndSpaListAsync(); | ||||
|                     if (NavigationManager.ToAbsoluteUri(NavigationManager.Uri).AbsolutePath == "/Login" || NavigationManager.ToAbsoluteUri(NavigationManager.Uri).AbsolutePath == "/") | ||||
|                         NavigationManager.NavigateTo(sameLevelMenus.FirstOrDefault(a => a.Id == data)?.Component ?? "index"); | ||||
|                     else | ||||
|                         NavigationManager.NavigateTo(NavigationManager.Uri); | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     NavigationManager.NavigateTo("index"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             await base.OnAfterRenderAsync(firstRender); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
| /// <summary> | ||||
| /// <20><>ҳ | ||||
| /// </summary> | ||||
| public partial class Index | ||||
| { | ||||
|     List<SysOperateLog> SysOperateLogs; | ||||
|  | ||||
|     List<SysVisitLog> SysVisitLogs; | ||||
|  | ||||
|     [Inject] | ||||
|     private InitTimezone InitTimezone { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         SysVisitLogs = (await App.GetService<IVisitLogService>().PageAsync(new() { Size = 5, Account = UserResoures.CurrentUser?.Account })).Records.ToList(); | ||||
|         SysOperateLogs = (await App.GetService<IOperateLogService>().PageAsync(new() { Size = 5, Account = UserResoures.CurrentUser?.Account })).Records.ToList(); | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
|  | ||||
| /// <summary> | ||||
| /// 操作日志 | ||||
| /// </summary> | ||||
| public partial class Oplog | ||||
| { | ||||
|     private readonly OperateLogPageInput search = new(); | ||||
|     private IAppDataTable _datatable; | ||||
|     private List<StringFilters> CategoryFilters { get; set; } = new(); | ||||
|     private List<StringFilters> ExeStatus { get; set; } = new(); | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         base.OnInitialized(); | ||||
|         CategoryFilters.Add(new StringFilters() { Key = "操作", Value = LogConst.LOG_OPERATE }); | ||||
|         CategoryFilters.Add(new StringFilters() { Key = "第三方操作", Value = LogConst.LOG_OPENAPIOPERATE }); | ||||
|         ExeStatus.Add(new StringFilters() { Key = "成功", Value = LogConst.LOG_SUCCESS }); | ||||
|         ExeStatus.Add(new StringFilters() { Key = "失败", Value = LogConst.LOG_FAIL }); | ||||
|     } | ||||
|  | ||||
|     private async Task ClearClick() | ||||
|     { | ||||
|         var confirm = await PopupService.OpenConfirmDialogAsync("删除", "确定 ?"); | ||||
|         if (confirm) | ||||
|         { | ||||
|             await OperateLogService.DeleteAsync(CategoryFilters.Select(it => it.Value).ToArray()); | ||||
|             await _datatable?.QueryClickAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private Task<SqlSugarPagedList<SysOperateLog>> QueryCallAsync(OperateLogPageInput input) | ||||
|     { | ||||
|         input.Account = search.Account; | ||||
|         input.Category = search.Category; | ||||
|         input.ExeStatus = search.ExeStatus; | ||||
|         return OperateLogService.PageAsync(input); | ||||
|     } | ||||
|     [Inject] | ||||
|     AjaxService AjaxService { get; set; } | ||||
|     async Task DownExportAsync(OperateLogPageInput input = null) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await AjaxService.DownFileAsync("file/operateLog", SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat(), input.Adapt<OperateLogInput>()); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,204 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/admin/role" | ||||
| @using System.Linq.Expressions; | ||||
| @using BlazorComponent; | ||||
| @using Masa.Blazor.Presets; | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using ThingsGateway.Admin.Application; | ||||
| @inject IRoleService SysRoleService | ||||
| @namespace ThingsGateway.Admin.Blazor | ||||
| @attribute [Authorize] | ||||
| @inject UserResoures UserResoures | ||||
| @inherits BaseComponentBase | ||||
| @layout MainLayout | ||||
| <AppDataTable @ref="_datatable" TItem="SysRole" SearchItem="RolePageInput" AddItem="RoleAddInput" EditItem="RoleEditInput" | ||||
|               SearchModel="@search" IsShowSearchKey | ||||
|               QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync" | ||||
|               EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync" | ||||
|                 IsShowQueryButton | ||||
|               IsShowAddButton=@UserResoures.IsHasButtonWithRole("sysroleadd") | ||||
|               IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("sysroledelete") | ||||
|               IsShowEditButton=@UserResoures.IsHasButtonWithRole("sysroleedit")> | ||||
|  | ||||
|     <AddTemplate> | ||||
|         @GetRenderFragment(context) | ||||
|  | ||||
|     </AddTemplate> | ||||
|  | ||||
|     <EditTemplate> | ||||
|         @GetRenderFragment(context) | ||||
|     </EditTemplate> | ||||
|  | ||||
|     <ItemColOperTemplate> | ||||
|         <MList Dense> | ||||
|             @if (@UserResoures.IsHasButtonWithRole("sysroleperresuorce")) | ||||
|             { | ||||
|                 <MListItem OnClick="async()=> | ||||
|                     { | ||||
|                     ChoiceRoleId=context.Item.Id; | ||||
|                     await ResuorceInitAsync(); | ||||
|                     IsShowResuorces=true; | ||||
|                     }"> | ||||
|                     <MListItemTitle Class="ml-2">资源权限</MListItemTitle> | ||||
|                 </MListItem> | ||||
|  | ||||
|             } | ||||
|             @if (@UserResoures.IsHasButtonWithRole("sysroleperuser")) | ||||
|             { | ||||
|                 <MListItem OnClick="async()=> | ||||
|                     { | ||||
|                     ChoiceRoleId=context.Item.Id; | ||||
|                     await UserInitAsync(); | ||||
|                     IsShowUsers=true; | ||||
|                     }"> | ||||
|                     <MListItemTitle Class="ml-2">授权用户</MListItemTitle> | ||||
|                 </MListItem> | ||||
|  | ||||
|             } | ||||
|         </MList> | ||||
|     </ItemColOperTemplate> | ||||
|  | ||||
| </AppDataTable> | ||||
|  | ||||
| <PDrawer @bind-Value="IsShowResuorces" OnCancel="() => IsShowResuorces = false" | ||||
|          Title=资源授权 | ||||
|          Width=@(IsMobile?"100%":"600") | ||||
|          MaxWidth="600" OnSave="OnRoleHasResuorcesSaveAsync"> | ||||
|     @if (IsShowResuorces) | ||||
|     { | ||||
|         <MSheet Outlined Class="ma-0 pa-2"> | ||||
|             <MRow Align="AlignTypes.Center"> | ||||
|                 <MCol>  <MLabel Class="ml-4 font-weight-black">菜单</MLabel>  </MCol> | ||||
|                 <MDivider Vertical /> | ||||
|                 <MCol>  <MLabel Class="ml-4 font-weight-black">按钮</MLabel>    </MCol> | ||||
|             </MRow> | ||||
|         </MSheet> | ||||
|         @foreach (var menu in ResTreeSelectors) | ||||
|         { | ||||
|             <MSheet Outlined Class="ma-0 pa-4"> | ||||
|                 <MRow Align="AlignTypes.Center"> | ||||
|                     <MCol> | ||||
|                         <MListItem IsActive=@(RoleHasResuorces.Any(it=>it.MenuId==menu.Id))> | ||||
|                             <ItemContent> | ||||
|                                 <MListItemContent> | ||||
|                                     <MListItemTitle>@menu.Title</MListItemTitle> | ||||
|                                 </MListItemContent> | ||||
|                                 <MListItemAction> | ||||
|                                     <MCheckbox TValue=bool Value="@context.Active" ValueChanged=@(enable=> | ||||
|                                        { | ||||
|                                        if(!enable) | ||||
|                                        RoleHasResuorces.RemoveWhere(it=>it.MenuId==menu.Id); | ||||
|                                        else if(!RoleHasResuorces.Any(it=>it.MenuId==menu.Id)) | ||||
|                                        RoleHasResuorces.Add(new() {MenuId=menu.Id}); | ||||
|                                        } | ||||
|                                        )></MCheckbox> | ||||
|                                 </MListItemAction> | ||||
|                             </ItemContent> | ||||
|                         </MListItem> | ||||
|                     </MCol> | ||||
|                     <MDivider Vertical /> | ||||
|                     <MCol> | ||||
|                         @GetButtonCore(menu) | ||||
|                     </MCol> | ||||
|  | ||||
|                 </MRow> | ||||
|  | ||||
|             </MSheet> | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| </PDrawer> | ||||
|  | ||||
| <PDrawer @bind-Value="IsShowUsers" OnCancel="() => IsShowUsers = false" | ||||
|          Title=授权用户 | ||||
|          Width=@(IsMobile?"100%":"500") | ||||
|          MaxWidth="500" OnSave="OnUsersSaveAsync"> | ||||
|  | ||||
|     <MCard Flat Class="ma-0 pa-4"> | ||||
|         <MCardTitle Class="py-2"> | ||||
|             <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1 mx-2 ml-6" @bind-Value="SearchKey" | ||||
|                         Outlined Label=@typeof(SysUser).GetDescription(nameof(SysUser.Account)) /> | ||||
|         </MCardTitle> | ||||
|         <MTreeview Class="my-1" Dense OpenAll TItem="UserSelectorOutput" TKey="UserSelectorOutput" Selectable @bind-Value="UsersChoice" | ||||
|         Items="AllUsers" ItemText="r=>r.Account" ItemChildren="r=>null" | ||||
|                    ItemKey=@(r=>r)> | ||||
|             <LabelContent> | ||||
|                 <span title=@context.Item.Account> | ||||
|                     @context.Item.Account | ||||
|                 </span> | ||||
|             </LabelContent> | ||||
|         </MTreeview> | ||||
|     </MCard> | ||||
|  | ||||
| </PDrawer> | ||||
|  | ||||
|  | ||||
| @code { | ||||
|     RenderFragment GetButtonCore(RoleGrantResourceMenu menu) | ||||
|     { | ||||
|         RenderFragment ViewSubMenu = null; | ||||
|  | ||||
|         foreach (var button in menu.Button ?? new()) | ||||
|         { | ||||
|             ViewSubMenu += | ||||
|     @<MListItem Class="ml-6" IsActive=@(RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id)?.ButtonInfo?.Contains(button.Id)==true)> | ||||
|         <ItemContent> | ||||
|             <MListItemContent> | ||||
|                 <MListItemTitle>@button.Title</MListItemTitle> | ||||
|             </MListItemContent> | ||||
|             <MListItemAction> | ||||
|                 <MCheckbox TValue=bool Value="@context.Active" ValueChanged=@(a=> | ||||
|                        { | ||||
|                        if(!a) | ||||
|                        { | ||||
|                        RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id)?.ButtonInfo?.RemoveWhere(it=>it==button.Id); | ||||
|                        } | ||||
|                        else | ||||
|                        { | ||||
|                        if( !(RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id)?.ButtonInfo?.Any(it=>it==button.Id)==true)) | ||||
|                        { | ||||
|                        if(!RoleHasResuorces.Any(it=>it.MenuId==menu.Id)) | ||||
|                        { | ||||
|                        RoleHasResuorces.Add(new() {MenuId=menu.Id}); | ||||
|                        } | ||||
|                        RoleHasResuorces.FirstOrDefault(it=>it.MenuId==menu.Id).ButtonInfo.Add(button.Id); | ||||
|                        } | ||||
|                        } | ||||
|                        })></MCheckbox> | ||||
|             </MListItemAction> | ||||
|         </ItemContent> | ||||
|     </MListItem> | ||||
|     ; | ||||
|         } | ||||
|  | ||||
|         return ViewSubMenu; | ||||
|     } | ||||
|  | ||||
|     RenderFragment GetRenderFragment(RoleAddInput context) | ||||
|     { | ||||
|         RenderFragment renderFragment = | ||||
|     @<div> | ||||
|         <MSubheader Class="mt-4 font-weight-black">    @(context.Description(x => x.Name))  </MSubheader> | ||||
|         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name /> | ||||
|  | ||||
|         <MSubheader Class="mt-4 mb-5 font-weight-black">@(context.Description(x => x.SortCode)) </MSubheader> | ||||
|         <MSlider @bind-Value=@context.SortCode Class="mb-5" TValue=int ThumbLabel="@("always")" Dense /> | ||||
|     </div> | ||||
|     ; | ||||
|         return renderFragment; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,118 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Masa.Blazor; | ||||
| using Masa.Blazor.Presets; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
| /// <summary> | ||||
| /// 角色页面 | ||||
| /// </summary> | ||||
| public partial class Role | ||||
| { | ||||
|     private readonly RolePageInput search = new(); | ||||
|     private IAppDataTable _datatable; | ||||
|     private List<UserSelectorOutput> AllUsers; | ||||
|     long ChoiceRoleId; | ||||
|     bool IsShowResuorces; | ||||
|     bool IsShowUsers; | ||||
|     List<RoleGrantResourceMenu> ResTreeSelectors = new(); | ||||
|     List<RelationRoleResuorce> RoleHasResuorces = new(); | ||||
|     private List<UserSelectorOutput> UsersChoice; | ||||
|  | ||||
|     [CascadingParameter] | ||||
|     MainLayout MainLayout { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     IResourceService ResourceService { get; set; } | ||||
|  | ||||
|     private string SearchKey { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     ISysUserService SysUserService { get; set; } | ||||
|  | ||||
|     private Task AddCallAsync(RoleAddInput input) | ||||
|     { | ||||
|         return SysRoleService.AddAsync(input); | ||||
|     } | ||||
|     private async Task DeleteCallAsync(IEnumerable<SysRole> sysRoles) | ||||
|     { | ||||
|         await SysRoleService.DeleteAsync(sysRoles.Select(a => a.Id).ToArray()); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task EditCallAsync(RoleEditInput input) | ||||
|     { | ||||
|         await SysRoleService.EditAsync(input); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|     private async Task OnRoleHasResuorcesSaveAsync(ModalActionEventArgs args) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GrantResourceInput userGrantRoleInput = new(); | ||||
|             var data = new List<SysResource>(); | ||||
|             userGrantRoleInput.Id = ChoiceRoleId; | ||||
|             userGrantRoleInput.GrantInfoList = RoleHasResuorces; | ||||
|             await SysRoleService.GrantResourceAsync(userGrantRoleInput); | ||||
|             IsShowResuorces = false; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             args.Cancel(); | ||||
|             await PopupService.EnqueueSnackbarAsync(ex, false); | ||||
|         } | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|     private async Task OnUsersSaveAsync(ModalActionEventArgs args) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GrantUserInput userGrantRoleInput = new(); | ||||
|             userGrantRoleInput.Id = ChoiceRoleId; | ||||
|             userGrantRoleInput.GrantInfoList = UsersChoice.Select(it => it.Id).ToList(); | ||||
|             await SysRoleService.GrantUserAsync(userGrantRoleInput); | ||||
|             IsShowUsers = false; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             args.Cancel(); | ||||
|             await PopupService.EnqueueSnackbarAsync(ex, false); | ||||
|         } | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     private Task<SqlSugarPagedList<SysRole>> QueryCallAsync(RolePageInput input) | ||||
|     { | ||||
|         return SysRoleService.PageAsync(input); | ||||
|     } | ||||
|  | ||||
|     private async Task ResuorceInitAsync() | ||||
|     { | ||||
|         ResTreeSelectors = (await ResourceService.GetRoleGrantResourceMenusAsync()); | ||||
|         RoleHasResuorces = (await SysRoleService.OwnResourceAsync(ChoiceRoleId))?.GrantInfoList; | ||||
|     } | ||||
|  | ||||
|     private async Task<List<UserSelectorOutput>> UserInitAsync() | ||||
|     { | ||||
|         AllUsers = await SysUserService.UserSelectorAsync(SearchKey); | ||||
|         var data = await SysRoleService.OwnUserAsync(ChoiceRoleId); | ||||
|         UsersChoice = AllUsers.Where(a => data.Contains(a.Id)).ToList(); | ||||
|         return AllUsers; | ||||
|     } | ||||
| } | ||||
| @@ -1,105 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Masa.Blazor; | ||||
| using Masa.Blazor.Presets; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
|  | ||||
| /// <summary> | ||||
| /// 用户界面 | ||||
| /// </summary> | ||||
| public partial class User | ||||
| { | ||||
|     private readonly UserPageInput search = new(); | ||||
|     private IAppDataTable _datatable; | ||||
|     private List<SysRole> AllRoles; | ||||
|     long ChoiceUserId; | ||||
|     bool IsShowRoles; | ||||
|     List<SysRole> RolesChoice = new(); | ||||
|     string SearchName; | ||||
|     [CascadingParameter] | ||||
|     MainLayout MainLayout { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     IRoleService SysRoleService { get; set; } | ||||
|     private Task AddCallAsync(UserAddInput input) | ||||
|     { | ||||
|         return SysUserService.AddAsync(input); | ||||
|     } | ||||
|     private async Task DeleteCallAsync(IEnumerable<SysUser> users) | ||||
|     { | ||||
|         await SysUserService.DeleteAsync(users.Select(a => a.Id).ToArray()); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task EditCallAsync(UserEditInput users) | ||||
|     { | ||||
|         await SysUserService.EditAsync(users); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task OnRolesSaveAsync(ModalActionEventArgs args) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             UserGrantRoleInput userGrantRoleInput = new(); | ||||
|             userGrantRoleInput.Id = ChoiceUserId; | ||||
|             userGrantRoleInput.RoleIdList = RolesChoice.Select(it => it.Id).ToList(); | ||||
|             await SysUserService.GrantRoleAsync(userGrantRoleInput); | ||||
|             IsShowRoles = false; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             args.Cancel(); | ||||
|             await PopupService.EnqueueSnackbarAsync(ex, false); | ||||
|         } | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|     private Task<SqlSugarPagedList<SysUser>> QueryCallAsync(UserPageInput input) | ||||
|     { | ||||
|         return SysUserService.PageAsync(input); | ||||
|     } | ||||
|  | ||||
|     private async Task ResetPasswordAsync(SysUser sysUser) | ||||
|     { | ||||
|         await SysUserService.ResetPasswordAsync(sysUser.Id); | ||||
|         await PopupService.EnqueueSnackbarAsync(new("成功", AlertTypes.Success)); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task RoleInitAsync() | ||||
|     { | ||||
|         AllRoles = await SysRoleService.RoleSelectorAsync(); | ||||
|         var data = await SysRoleService.GetRoleIdListByUserIdAsync(ChoiceUserId); | ||||
|         RolesChoice = AllRoles.Where(a => data.Contains(a.Id)).ToList(); | ||||
|     } | ||||
|     private async Task UserStatusChangeAsync(SysUser context, bool enable) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (enable) | ||||
|                 await SysUserService.EnableUserAsync(context.Id); | ||||
|             else | ||||
|                 await SysUserService.DisableUserAsync(context.Id); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             await _datatable?.QueryClickAsync(); | ||||
|             await MainLayout.StateHasChangedAsync(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,83 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Masa.Blazor; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
| /// <summary> | ||||
| /// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
| /// </summary> | ||||
| public partial class UserCenter | ||||
| { | ||||
|     List<long> _menusChoice = new(); | ||||
|     readonly PasswordInfoInput _passwordInfoInput = new(); | ||||
|     readonly UpdateInfoInput _updateInfoInput = new(); | ||||
|  | ||||
|     long DefaultMenuId { get; set; } | ||||
|     [CascadingParameter] | ||||
|     MainLayout MainLayout { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     NavigationManager NavigationManager { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     IUserCenterService UserCenterService { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         DefaultMenuId = await App.GetService<IUserCenterService>().GetLoginDefaultRazorAsync(UserManager.UserId); | ||||
|         _updateInfoInput.Email = UserResoures.CurrentUser.Email; | ||||
|         _updateInfoInput.Phone = UserResoures.CurrentUser.Phone; | ||||
|         _menusChoice = await App.GetService<IUserCenterService>().GetLoginWorkbenchAsync(); | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
|     async Task OnDefaultRazorSaveAsync() | ||||
|     { | ||||
|         await UserCenterService.UpdateUserDefaultRazorAsync(UserManager.UserId, DefaultMenuId); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|         await PopupService.EnqueueSnackbarAsync("<22>ɹ<EFBFBD>", AlertTypes.Success); | ||||
|     } | ||||
|  | ||||
|     async Task OnShortcutSaveAsync() | ||||
|     { | ||||
|         await UserCenterService.UpdateWorkbenchAsync(_menusChoice); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|         await PopupService.EnqueueSnackbarAsync("<22>ɹ<EFBFBD>", AlertTypes.Success); | ||||
|     } | ||||
|     async Task OnUpdatePasswordInfoAsync(FormContext context) | ||||
|     { | ||||
|         var success = context.Validate(); | ||||
|         if (success) | ||||
|         { | ||||
|             //<2F><>֤<EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><D2B5> | ||||
|             _passwordInfoInput.Id = UserResoures.CurrentUser.Id; | ||||
|             await UserCenterService.EditPasswordAsync(_passwordInfoInput); | ||||
|             await MainLayout.StateHasChangedAsync(); | ||||
|             await PopupService.EnqueueSnackbarAsync("<22>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD>¼", AlertTypes.Success); | ||||
|             await Task.Delay(2000); | ||||
|             NavigationManager.NavigateTo(NavigationManager.Uri); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async Task OnUpdateUserInfoAsync() | ||||
|     { | ||||
|         await UserCenterService.UpdateUserInfoAsync(_updateInfoInput); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|         await PopupService.EnqueueSnackbarAsync("<22>ɹ<EFBFBD>", AlertTypes.Success); | ||||
|     } | ||||
| } | ||||
| @@ -1,102 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/login" | ||||
| @layout BaseLayout | ||||
| @inherits BaseComponentBase | ||||
| @namespace ThingsGateway.Admin.Blazor | ||||
| @using BlazorComponent; | ||||
| @using Masa.Blazor.Presets; | ||||
|  | ||||
| <Ajax></Ajax> | ||||
| @if (IsMobile) | ||||
| { | ||||
|     <MCard @onkeydown=Enter Height=@("100%") > | ||||
|         @GetLoginCore() | ||||
|     </MCard> | ||||
| } | ||||
| else | ||||
| { | ||||
|     <MRow NoGutters Style="height:100%"> | ||||
|         <MCol Md=5 Sm=12> | ||||
|             <MSheet Elevation=1 Style="width:100%; height:100%;" Class="d-flex align-start flex-column mb-6"> | ||||
|                 <div class="d-flex align-center ml-12 mt-12"> | ||||
|                     <MAvatar Size="40" Color="teal"> | ||||
|                         <span class="white--text text-h6">@CONFIG_TITLE?.GetNameLen2()</span> | ||||
|                     </MAvatar> | ||||
|                     <h1>@CONFIG_TITLE</h1> | ||||
|                 </div> | ||||
|                 <div class="d-flex align-center ml-12 mt-12 mb-auto"> | ||||
|                     <h3>@CONFIG_REMARK</h3> | ||||
|                 </div> | ||||
|                 <div class="d-flex align-center pa-2" style="width:100%;height:100%;"> | ||||
|                     <MImage Src=@(BlazorResourceConst.ResourceUrl+"images/login-left.svg")></MImage> | ||||
|                 </div> | ||||
|             </MSheet> | ||||
|  | ||||
|         </MCol> | ||||
|  | ||||
|         <MCol Md=7 Sm=12 Align="AlignTypes.Center"> | ||||
|             <MRow Md=6 Sm=12 Justify="JustifyTypes.Center" Align="AlignTypes.Center"> | ||||
|                 <MCard Class="px-16 py-12" @onkeydown=Enter> | ||||
|                     @GetLoginCore() | ||||
|                 </MCard> | ||||
|             </MRow> | ||||
|         </MCol> | ||||
|  | ||||
|     </MRow> | ||||
|  | ||||
| } | ||||
|  | ||||
| @code { | ||||
|  | ||||
|     RenderFragment GetLoginCore() | ||||
|     { | ||||
|         RenderFragment ViewSubMenu = | ||||
|     @<div class="mt-2 px-2 py-1 mx-auto text-center my-auto" > | ||||
|         <MAvatar Size=80> | ||||
|             <MImage Src=@UserLogoUrl> | ||||
|             </MImage> | ||||
|         </MAvatar> | ||||
|         <h5 class="mt-2 mb-12">@Welcome 👋</h5> | ||||
|         <MTextField TValue="string" | ||||
|                     Label=账号 | ||||
|                             Outlined | ||||
|                     HideDetails="@("auto")" | ||||
|         @bind-Value=@loginModel.Account> | ||||
|         </MTextField> | ||||
|         <MTextField TValue="string" | ||||
|                     Class="mt-10" | ||||
|                     Label="密码" | ||||
|                     Type="@(_showPassword ? "text" : "password")" | ||||
|                     AppendIcon="@(_showPassword ? "mdi-eye" : "mdi-eye-off")" | ||||
|                     OnAppendClick="()=>_showPassword = !_showPassword" | ||||
|                             Outlined | ||||
|                     HideDetails="@("auto")" | ||||
|         @bind-Value=@Password> | ||||
|         </MTextField> | ||||
|         @if (_showCaptcha) | ||||
|         { | ||||
|             <PImageCaptcha @ref=captcha @bind-Value="CaptchaValue" | ||||
|                            TextFieldClass="mt-10 mx-auto" | ||||
|                            Height="60" | ||||
|                            Label=验证码 Outlined | ||||
|                                OnRefresh="RefreshCode" | ||||
|                                ErrorMessage=验证码错误> | ||||
|                 </PImageCaptcha> | ||||
|         } | ||||
|         <MButton Class="mt-11 rounded-4" OnClick=LoginAsync Height=46 Width=@("100%") Color="primary">登录</MButton> | ||||
|     </div> | ||||
|         ; | ||||
|         return ViewSubMenu; | ||||
|     } | ||||
| } | ||||
| @@ -1,151 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DataEncryption; | ||||
|  | ||||
| using Masa.Blazor.Presets; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Web; | ||||
| using Microsoft.Extensions.Hosting; | ||||
|  | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
|  | ||||
| /// <summary> | ||||
| /// 登录页面 | ||||
| /// </summary> | ||||
| public partial class Login | ||||
| { | ||||
|     private string CaptchaValue; | ||||
|     bool _showPassword; | ||||
|     bool _showCaptcha; | ||||
|     private readonly LoginInput loginModel = new(); | ||||
|  | ||||
|  | ||||
|     [Inject] | ||||
|     AjaxService AjaxService { get; set; } | ||||
|  | ||||
|  | ||||
|     [Inject] | ||||
|     IAuthService AuthService { get; set; } | ||||
|  | ||||
|  | ||||
|  | ||||
|     string UserLogoUrl { get; set; } = BlazorResourceConst.ResourceUrl + "images/defaultUser.svg"; | ||||
|  | ||||
|     string Welcome { get; set; } | ||||
|  | ||||
|     private ValidCodeOutput CaptchaInfo { get; set; } | ||||
|  | ||||
|     private string Password { get; set; } | ||||
|  | ||||
|     private string CONFIG_REMARK { get; set; } | ||||
|  | ||||
|     private string CONFIG_TITLE { get; set; } | ||||
|  | ||||
|     private async Task Enter(KeyboardEventArgs e) | ||||
|     { | ||||
|         if (e.Code == "Enter" || e.Code == "NumpadEnter") | ||||
|         { | ||||
|             await LoginAsync(); | ||||
|         } | ||||
|     } | ||||
|     private PImageCaptcha captcha; | ||||
|     [Inject] | ||||
|     IUserCenterService UserCenterService { get; set; } | ||||
|     [Inject] | ||||
|     IResourceService ResourceService { get; set; } | ||||
|     [Inject] | ||||
|     ISysUserService SysUserService { get; set; } | ||||
|     private async Task LoginAsync() | ||||
|     { | ||||
|         loginModel.ValidCodeReqNo = CaptchaInfo.ValidCodeReqNo; | ||||
|         loginModel.ValidCode = CaptchaValue; | ||||
|         loginModel.Password = DESCEncryption.Encrypt(Password, DESCKeyConst.DESCKey); | ||||
|         if (IsMobile) | ||||
|         { | ||||
|             loginModel.Device = AuthDeviceTypeEnum.APP; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             loginModel.Device = AuthDeviceTypeEnum.PC; | ||||
|         } | ||||
|  | ||||
|         var ajaxOption = new AjaxOption { Url = "/auth/b/login", Data = loginModel, }; | ||||
|         var str = await AjaxService.GetMessageAsync(ajaxOption); | ||||
|         if (str != null) | ||||
|         { | ||||
|             var ret = str.ToJsonWithT<UnifyResult<LoginOutput>>(); | ||||
|             if (ret.Code != 200) | ||||
|             { | ||||
|                 if (captcha != null) | ||||
|                 { | ||||
|                     await captcha.RefreshCode(); | ||||
|                 } | ||||
|                 await PopupService.EnqueueSnackbarAsync(new("登录错误" + ": " + ret.Msg.ToString(), AlertTypes.Error)); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 await PopupService.EnqueueSnackbarAsync(new("登录成功", AlertTypes.Success)); | ||||
|                 await Task.Delay(500); | ||||
|                 var userId = await SysUserService.GetIdByAccountAsync(loginModel.Account); | ||||
|                 var data = await UserCenterService.GetLoginDefaultRazorAsync(userId); | ||||
|                 var sameLevelMenus = await ResourceService.GetaMenuAndSpaListAsync(); | ||||
|                 if (NavigationManager.ToAbsoluteUri(NavigationManager.Uri).AbsolutePath == "/Login" || NavigationManager.ToAbsoluteUri(NavigationManager.Uri).AbsolutePath == "/") | ||||
|                     await AjaxService.GotoAsync(sameLevelMenus.FirstOrDefault(a => a.Id == data)?.Component ?? "index"); | ||||
|                 else | ||||
|                     await AjaxService.GotoAsync(NavigationManager.Uri); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (captcha != null) | ||||
|             { | ||||
|                 await captcha.RefreshCode(); | ||||
|             } | ||||
|             await PopupService.EnqueueSnackbarAsync(new("登录错误", AlertTypes.Error)); | ||||
|         } | ||||
|     } | ||||
|     [Inject] | ||||
|     private NavigationManager NavigationManager { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         if (App.HostEnvironment.IsDevelopment()) | ||||
|         { | ||||
|             loginModel.Account = "superAdmin"; | ||||
|             Password = "111111"; | ||||
|         } | ||||
|         GetCaptchaInfo(); | ||||
|         CONFIG_TITLE = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_TITLE))?.ConfigValue; | ||||
|         CONFIG_REMARK = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_REMARK))?.ConfigValue; | ||||
|         _showCaptcha = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_CAPTCHA_OPEN))?.ConfigValue?.ToBoolean() == true; | ||||
|         Welcome = "欢迎使用" + CONFIG_TITLE + "!"; | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
|     private void GetCaptchaInfo() | ||||
|     { | ||||
|         CaptchaInfo = AuthService.GetCaptchaInfo(); | ||||
|     } | ||||
|  | ||||
|     private Task<string> RefreshCode() | ||||
|     { | ||||
|         CaptchaInfo = AuthService.GetCaptchaInfo(); | ||||
|         return Task.FromResult(CaptchaInfo.CodeValue); | ||||
|     } | ||||
| } | ||||
| @@ -1,74 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Blazor; | ||||
|  | ||||
| /// <summary> | ||||
| /// Layout | ||||
| /// </summary> | ||||
| public partial class MainLayout | ||||
| { | ||||
|     private static readonly string[] selfPatterns = | ||||
|     { | ||||
|     }; | ||||
|     bool Changed { get; set; } | ||||
|     private bool? _drawerOpen = true; | ||||
|  | ||||
|     private PageTabs _pageTabs; | ||||
|  | ||||
|     private string CONFIG_COPYRIGHT = ""; | ||||
|  | ||||
|     private string CONFIG_COPYRIGHT_URL = ""; | ||||
|  | ||||
|     private string CONFIG_TITLE = ""; | ||||
|     /// <summary> | ||||
|     /// IsMobile | ||||
|     /// </summary> | ||||
|     [CascadingParameter(Name = "IsMobile")] | ||||
|     public bool IsMobile { get; set; } | ||||
|  | ||||
|  | ||||
|  | ||||
|     private List<NavItem> Navs { get; set; } = new(); | ||||
|  | ||||
|     [Inject] | ||||
|     private UserResoures UserResoures { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 页面刷新 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task StateHasChangedAsync() | ||||
|     { | ||||
|         CONFIG_COPYRIGHT = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_COPYRIGHT)).ConfigValue; | ||||
|         CONFIG_TITLE = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_TITLE)).ConfigValue; | ||||
|         CONFIG_COPYRIGHT_URL = (await App.GetService<IConfigService>().GetByConfigKeyAsync(ConfigConst.SYS_CONFIGBASEDEFAULT, ConfigConst.CONFIG_COPYRIGHT_URL)).ConfigValue; | ||||
|         await UserResoures.InitUserAsync(); | ||||
|         await UserResoures.InitMenuAsync(); | ||||
|         Navs = UserResoures.Menus.Parse(); | ||||
|         Changed = !Changed; | ||||
|         await InvokeAsync(StateHasChanged); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|         await StateHasChangedAsync(); | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,14 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Admin.Blazor.Core\ThingsGateway.Admin.Blazor.Core.csproj" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| @@ -1,363 +0,0 @@ | ||||
| <?xml version="1.0"?> | ||||
| <doc> | ||||
|     <assembly> | ||||
|         <name>ThingsGateway.Admin.Blazor</name> | ||||
|     </assembly> | ||||
|     <members> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.AppDataTable`4"> | ||||
|             <summary> | ||||
|             通用表格 | ||||
|             </summary> | ||||
|             <typeparam name="TItem"></typeparam> | ||||
|             <typeparam name="SearchItem"></typeparam> | ||||
|             <typeparam name="AddItem"></typeparam> | ||||
|             <typeparam name="EditItem"></typeparam> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.AddCallAsync"> | ||||
|             <summary> | ||||
|             添加项委托 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.AddTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 添加模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ClassString"> | ||||
|             <summary> | ||||
|             MSheet.Class | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.StyleString"> | ||||
|             <summary> | ||||
|             MSheet.Style | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.DeleteCallAsync"> | ||||
|             <summary> | ||||
|             删除项委托 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Dense"> | ||||
|             <summary> | ||||
|             表格紧凑 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.EditCallAsync"> | ||||
|             <summary> | ||||
|             编辑项委托 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.EditTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 编辑模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Detailemplate"> | ||||
|             <summary> | ||||
|             获得/设置 详情模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.FilterHeaders"> | ||||
|             <summary> | ||||
|             表头过滤,返回DataTableHeader列表,传输参数已包含全部初始表头与表头标题 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Filters"> | ||||
|             <summary> | ||||
|             表头过滤之后执行的方法,返回Filter值,ture则显示,false则隐藏 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.HeaderTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 Table Header 模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsMenuOperTemplate"> | ||||
|             <summary> | ||||
|             右侧操作栏以菜单形式显示 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsPage"> | ||||
|             <summary> | ||||
|             是否分页 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowAddButton"> | ||||
|             <summary> | ||||
|             是否显示添加按钮 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowClearSearch"> | ||||
|             <summary> | ||||
|             是否显示清空搜索 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowDeleteButton"> | ||||
|             <summary> | ||||
|             是否显示删除按钮 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowDetailButton"> | ||||
|             <summary> | ||||
|             是否显示详情按钮 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowEditButton"> | ||||
|             <summary> | ||||
|             是否显示编辑按钮 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowFilter"> | ||||
|             <summary> | ||||
|             是否显示过滤 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowOperCol"> | ||||
|             <summary> | ||||
|             是否显示右侧操作栏 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowQueryButton"> | ||||
|             <summary> | ||||
|             是否显示查询按钮 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowSearchKey"> | ||||
|             <summary> | ||||
|             是否显示搜索关键字 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowSelect"> | ||||
|             <summary> | ||||
|             是否显示表格多项选择 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.IsShowToolbar"> | ||||
|             <summary> | ||||
|             是否显示顶部操作工具栏 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ItemColOperTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 Table Oper 模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ItemColTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 Table Cols 模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.ItemColWithDTTemplate"> | ||||
|             <summary> | ||||
|             独立设置 Table Cols 模板,需自行实现DateTime类型的时区转换 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.Items"> | ||||
|             <summary> | ||||
|             当前显示项目 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.OtherToolbarTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 其他操作栏模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.PageSizeItems"> | ||||
|             <summary> | ||||
|             分页选择项目 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.QueryCallAsync"> | ||||
|             <summary> | ||||
|             查询项委托 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.SearchModel"> | ||||
|             <summary> | ||||
|             获得/设置 SearchModel 实例 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.SearchTemplate"> | ||||
|             <summary> | ||||
|             获得/设置 查询与操作栏模板 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.QueryClickAsync"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.OnAfterRenderAsync(System.Boolean)"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.AppDataTable`4.OnInitializedAsync"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Core.IAppDataTable"> | ||||
|             <summary> | ||||
|             通用表格 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Core.IAppDataTable.QueryClickAsync"> | ||||
|             <summary> | ||||
|             查询刷新 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.StringFilters"> | ||||
|             <summary> | ||||
|             键值表示 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.StringFilters.Key"> | ||||
|             <summary> | ||||
|             键 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.StringFilters.Value"> | ||||
|             <summary> | ||||
|             值 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Base"> | ||||
|             <summary> | ||||
|             Base | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Base.OnAfterRenderAsync(System.Boolean)"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|             <param name="firstRender"></param> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Config"> | ||||
|             <summary> | ||||
|             系统配置页面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Config.OnParametersSetAsync"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Index"> | ||||
|             <summary> | ||||
|             首页 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Index.OnParametersSetAsync"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Menu"> | ||||
|             <summary> | ||||
|             菜单页面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Menu.OnParametersSetAsync"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.OpenApiSession"> | ||||
|             <summary> | ||||
|             OpenApiSession | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.OpenApiUserR"> | ||||
|             <summary> | ||||
|             OpenApiUserR | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Oplog"> | ||||
|             <summary> | ||||
|             操作日志 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Oplog.OnInitialized"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Role"> | ||||
|             <summary> | ||||
|             角色页面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Session"> | ||||
|             <summary> | ||||
|             会话页面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Spa"> | ||||
|             <summary> | ||||
|             SPA | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.User"> | ||||
|             <summary> | ||||
|             用户界面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.UserCenter"> | ||||
|             <summary> | ||||
|             个人设置 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.UserCenter.OnParametersSetAsync"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Vislog"> | ||||
|             <summary> | ||||
|             访问日志页面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Vislog.CategoryFilters"> | ||||
|             <summary> | ||||
|             日志分类菜单 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.Vislog.ExeStatus"> | ||||
|             <summary> | ||||
|             执行结果菜单 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Vislog.OnInitialized"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.Login"> | ||||
|             <summary> | ||||
|             登录页面 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.Login.OnParametersSetAsync"> | ||||
|             <inheritdoc/> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Admin.Blazor.MainLayout"> | ||||
|             <summary> | ||||
|             Layout | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:ThingsGateway.Admin.Blazor.MainLayout.IsMobile"> | ||||
|             <summary> | ||||
|             IsMobile | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.MainLayout.StateHasChangedAsync"> | ||||
|             <summary> | ||||
|             页面刷新 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Admin.Blazor.MainLayout.OnInitializedAsync"> | ||||
|             <summary> | ||||
|             <inheritdoc/> | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
| @@ -1,81 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.Caching.Memory; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// 系统内存缓存 | ||||
| /// </summary> | ||||
| public class SysMemoryCache : IDisposable | ||||
| { | ||||
|     private readonly MemoryCache _cache; | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="SysMemoryCache"/> | ||||
|     /// </summary> | ||||
|     public SysMemoryCache() | ||||
|     { | ||||
|         _cache = new MemoryCache(new MemoryCacheOptions()); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public T Get<T>(string key, bool mapster) | ||||
|     { | ||||
|         return _cache.TryGetValue<T>(key, out var value) ? mapster ? value.Adapt<T>() : value : default; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> func, bool mapster) where T : class | ||||
|     { | ||||
|         var value = await _cache.GetOrCreateAsync(key, func); | ||||
|         return mapster ? value.Adapt<T>() : value; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public T GetOrCreate<T>(string key, Func<ICacheEntry, T> func, bool mapster) where T : class | ||||
|     { | ||||
|         var value = _cache.GetOrCreate(key, func); | ||||
|         return mapster ? value.Adapt<T>() : value; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Remove(object key) | ||||
|     { | ||||
|         _cache.Remove(key); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Set<T>(object key, T value, bool mapster) | ||||
|     { | ||||
|         _cache.Set(key, mapster ? value.Adapt<T>() : value); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Set<T>(object key, T value, TimeSpan offset, bool mapster) | ||||
|     { | ||||
|         _cache.Set(key, mapster ? value.Adapt<T>() : value, offset); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _cache?.Dispose(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,93 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.Collections; | ||||
| using System.Data; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// 对象拓展 | ||||
| /// </summary> | ||||
| [SuppressSniffer] | ||||
| public static class ListExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// List转DataTable | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="list"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DataTable ToDataTable<T>(this List<T> list) | ||||
|     { | ||||
|         DataTable result = new(); | ||||
|         if (list.Count > 0) | ||||
|         { | ||||
|             var propertys = list[0].GetType().GetPropertiesWithCache(); | ||||
|             foreach (PropertyInfo pi in propertys) | ||||
|             { | ||||
|                 Type colType = pi.PropertyType; | ||||
|                 if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>)) | ||||
|                 { | ||||
|                     colType = colType.GetGenericArguments().First(); | ||||
|                 } | ||||
|                 if (IsIgnoreColumn(pi)) | ||||
|                     continue; | ||||
|                 if (IsJsonColumn(pi))//如果是json特性就是sting类型 | ||||
|                     colType = typeof(string); | ||||
|                 if (colType.IsEnum)//如果是Enum需要转string才会保存Enum字符串 | ||||
|                     colType = typeof(string); | ||||
|                 result.Columns.Add(pi.Name, colType); | ||||
|             } | ||||
|             for (int i = 0; i < list.Count; i++) | ||||
|             { | ||||
|                 ArrayList tempList = new(); | ||||
|                 foreach (PropertyInfo pi in propertys) | ||||
|                 { | ||||
|                     if (IsIgnoreColumn(pi)) | ||||
|                         continue; | ||||
|                     object obj = pi.GetValue(list[i], null); | ||||
|                     if (IsJsonColumn(pi))//如果是json特性就是转化为json格式 | ||||
|                         obj = obj?.ToJsonString();//如果json字符串是空就传null | ||||
|                     tempList.Add(obj); | ||||
|                 } | ||||
|                 object[] array = tempList.ToArray(); | ||||
|                 result.LoadDataRow(array, true); | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// SqlSugar是否忽略字段 | ||||
|     /// </summary> | ||||
|     /// <param name="pi"></param> | ||||
|     /// <returns></returns> | ||||
|     private static bool IsIgnoreColumn(PropertyInfo pi) | ||||
|     { | ||||
|         return pi.GetCustomAttribute<SugarColumn>(false).IsIgnore == true; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// SqlSugar是否Json字段 | ||||
|     /// </summary> | ||||
|     /// <param name="pi"></param> | ||||
|     /// <returns></returns> | ||||
|     private static bool IsJsonColumn(PropertyInfo pi) | ||||
|     { | ||||
|         return pi.GetCustomAttribute<SugarColumn>(false).IsJson == true; | ||||
|     } | ||||
| } | ||||
| @@ -1,154 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// 对象拓展 | ||||
| /// </summary> | ||||
| [SuppressSniffer] | ||||
| public static class StringExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 返回List,无其他处理 | ||||
|     /// </summary> | ||||
|     public static List<string> StringToList(this string str) | ||||
|     { | ||||
|         return new List<string>() { str }; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用 正则表达式 判断字符是不是汉字 | ||||
|     /// </summary> | ||||
|     /// <param name="text">待判断字符或字符串</param> | ||||
|     /// <returns>真:是汉字;假:不是</returns> | ||||
|     private static bool IsChinese(string text) => Regex.IsMatch(text, @"[\u4e00-\u9fbb]"); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取字符串中的两个字符作为名称简述 | ||||
|     /// </summary> | ||||
|     /// <param name="name"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string GetNameLen2(this string name) | ||||
|     { | ||||
|         if (name.IsNullOrEmpty()) | ||||
|             return string.Empty; | ||||
|         var nameLength = name.Length;//获取姓名长度 | ||||
|         string nameWritten = name;//需要绘制的文字 | ||||
|         if (nameLength > 2)//如果名字长度超过2个 | ||||
|         { | ||||
|             // 如果用户输入的姓名大于等于3个字符,截取后面两位 | ||||
|             string firstName = name.Substring(0, 1); | ||||
|             if (IsChinese(firstName)) | ||||
|             { | ||||
|                 // 截取倒数两位汉字 | ||||
|                 nameWritten = name.Substring(name.Length - 2); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // 截取第一个英文字母和第二个大写的字母 | ||||
|                 var data = Regex.Match(name, @"[A-Z]?[a-z]+([A-Z])").Value; | ||||
|                 nameWritten = data.FirstCharToUpper() + data.LastCharToUpper(); | ||||
|                 if (nameWritten.IsNullOrEmpty()) | ||||
|                 { | ||||
|                     nameWritten = name.FirstCharToUpper() + name.LastCharToUpper(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return nameWritten; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 字符串是 null 或者 空 | ||||
|     /// </summary> | ||||
|     /// <param name="value"></param> | ||||
|     /// <returns></returns> | ||||
|     public static bool IsNullOrEmpty(this string value) => value == null || value!.Length <= 0; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回字符串首字符的大写字母 | ||||
|     /// </summary> | ||||
|     /// <param name="input"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string FirstCharToUpper(this string input) => input.IsNullOrEmpty() ? input : input.First().ToString().ToUpper(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回字符串尾字符的大写字母 | ||||
|     /// </summary> | ||||
|     /// <param name="input"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string LastCharToUpper(this string input) => input.IsNullOrEmpty() ? input : input.Last().ToString().ToUpper(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 转换布尔值 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static bool ToBoolean(this string value, bool defaultValue = false) => value?.ToUpper() switch | ||||
|     { | ||||
|         "1" or "TRUE" => true, | ||||
|         _ => defaultValue, | ||||
|     }; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// ToLong | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static long ToLong(this string value, long defaultValue = 0) => value.IsNullOrEmpty() ? defaultValue : Int64.TryParse(value, out var n) ? n : defaultValue; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// ToInt | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static int ToInt(this string value, int defaultValue = 0) => value.IsNullOrEmpty() ? defaultValue : Int32.TryParse(value, out var n) ? n : defaultValue; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// ToDecimal | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static decimal ToDecimal(this string value, int defaultValue = 0) => value.IsNullOrEmpty() ? defaultValue : Decimal.TryParse(value, out var n) ? n : defaultValue; | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 匹配手机号码 | ||||
|     /// </summary> | ||||
|     /// <param name="s">源字符串</param> | ||||
|     /// <returns>是否匹配成功</returns> | ||||
|     public static bool MatchPhoneNumber(this string s) => !string.IsNullOrEmpty(s) && Regex.IsMatch(s, @"^1[3456789][0-9]{9}$"); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 匹配邮箱格式 | ||||
|     /// </summary> | ||||
|     /// <param name="s">源字符串</param> | ||||
|     /// <returns>是否匹配成功</returns> | ||||
|     public static bool MatchEmail(this string s) => !string.IsNullOrEmpty(s) && Regex.IsMatch(s, @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"); | ||||
|  | ||||
|     /// <summary>合并多段路径</summary> | ||||
|     /// <param name="path"></param> | ||||
|     /// <param name="ps"></param> | ||||
|     /// <returns></returns> | ||||
|     public static string CombinePath(this string path, params string[] ps) | ||||
|     { | ||||
|         if (ps == null || ps.Length <= 0) return path; | ||||
|         path ??= string.Empty; | ||||
|  | ||||
|         foreach (var item in ps) | ||||
|         { | ||||
|             if (!item.IsNullOrEmpty()) path = Path.Combine(path, item); | ||||
|         } | ||||
|         return path; | ||||
|     } | ||||
| } | ||||
| @@ -1,183 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// 对象拓展 | ||||
| /// </summary> | ||||
| [SuppressSniffer] | ||||
| public static class SysDateTimeExtensions | ||||
| { | ||||
|     private static readonly DateTime _dt1970 = new(1970, 1, 1); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 系统默认使用的当前时间 | ||||
|     /// </summary> | ||||
|     public static DateTime CurrentDateTime => DateTime.Now; | ||||
|     /// <summary> | ||||
|     /// 返回yyyy-MM-dd HH:mm:ss:fff zz时间格式字符串 | ||||
|     /// </summary> | ||||
|     public static string ToDefaultDateTimeFormat(this in DateTime dt, TimeSpan offset) | ||||
|     { | ||||
|         if (dt.Kind == DateTimeKind.Utc) | ||||
|             return new DateTimeOffset(dt.ToLocalTime(), offset).ToString("yyyy-MM-dd HH:mm:ss:fff zz"); | ||||
|         else if (dt == DateTime.MinValue || dt == DateTime.MaxValue) | ||||
|             return dt.ToString("yyyy-MM-dd HH:mm:ss:fff zz"); | ||||
|         else | ||||
|             return new DateTimeOffset(dt, offset).ToString("yyyy-MM-dd HH:mm:ss:fff zz"); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 返回yyyy-MM-dd HH:mm:ss:fff zz时间格式字符串 | ||||
|     /// </summary> | ||||
|     public static string ToDefaultDateTimeFormat(this in DateTime dt) | ||||
|     { | ||||
|         return dt.ToString("yyyy-MM-dd HH:mm:ss:fff zz"); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 返回yyyy-MM-dd HH:mm:ss:fff zz时间格式字符串 | ||||
|     /// </summary> | ||||
|     public static string ToFileDateTimeFormat(this in DateTime dt) | ||||
|     { | ||||
|         return dt.ToString("yyyy-MM-dd HH-mm-ss-fff zz"); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 计算2个时间差,返回文字描述 | ||||
|     /// </summary> | ||||
|     /// <param name="beginTime">开始时间</param> | ||||
|     /// <param name="endTime">结束时间</param> | ||||
|     /// <returns>时间差</returns> | ||||
|     public static string GetDiffTime(this in DateTime beginTime, in DateTime endTime) | ||||
|     { | ||||
|         string strResout = string.Empty; | ||||
|  | ||||
|         //获得2时间的时间间隔秒计算 | ||||
|         TimeSpan span = endTime.Subtract(beginTime); | ||||
|         int sec = Convert.ToInt32(span.TotalSeconds); | ||||
|         int minutes = 1 * 60; | ||||
|         int hours = minutes * 60; | ||||
|         int day = hours * 24; | ||||
|         int month = day * 30; | ||||
|         int year = month * 12; | ||||
|  | ||||
|         //提醒时间,到了返回1,否则返回0 | ||||
|         if (sec > year) | ||||
|         { | ||||
|             strResout += (sec / year) + "年"; | ||||
|             sec %= year; //剩余 | ||||
|         } | ||||
|  | ||||
|         if (sec > month) | ||||
|         { | ||||
|             strResout += (sec / month) + "月"; | ||||
|             sec %= month; | ||||
|         } | ||||
|  | ||||
|         if (sec > day) | ||||
|         { | ||||
|             strResout += (sec / day) + "天"; | ||||
|             sec %= day; | ||||
|         } | ||||
|  | ||||
|         if (sec > hours) | ||||
|         { | ||||
|             strResout += (sec / hours) + "小时"; | ||||
|             sec %= hours; | ||||
|         } | ||||
|  | ||||
|         if (sec > minutes) | ||||
|         { | ||||
|             strResout += (sec / minutes) + "分"; | ||||
|             sec %= minutes; | ||||
|         } | ||||
|  | ||||
|         strResout += sec + "秒"; | ||||
|         return strResout; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 计算2个时间差,返回文字描述 | ||||
|     /// </summary> | ||||
|     /// <param name="beginTime">开始时间</param> | ||||
|     /// <param name="endTime">结束时间</param> | ||||
|     /// <returns>时间差</returns> | ||||
|     public static string GetDiffTime(this in DateTimeOffset beginTime, in DateTimeOffset endTime) | ||||
|     { | ||||
|         string strResout = string.Empty; | ||||
|  | ||||
|         //获得2时间的时间间隔秒计算 | ||||
|         TimeSpan span = endTime.Subtract(beginTime); | ||||
|         int sec = Convert.ToInt32(span.TotalSeconds); | ||||
|         int minutes = 1 * 60; | ||||
|         int hours = minutes * 60; | ||||
|         int day = hours * 24; | ||||
|         int month = day * 30; | ||||
|         int year = month * 12; | ||||
|  | ||||
|         //提醒时间,到了返回1,否则返回0 | ||||
|         if (sec > year) | ||||
|         { | ||||
|             strResout += (sec / year) + "年"; | ||||
|             sec %= year; //剩余 | ||||
|         } | ||||
|  | ||||
|         if (sec > month) | ||||
|         { | ||||
|             strResout += (sec / month) + "月"; | ||||
|             sec %= month; | ||||
|         } | ||||
|  | ||||
|         if (sec > day) | ||||
|         { | ||||
|             strResout += (sec / day) + "天"; | ||||
|             sec %= day; | ||||
|         } | ||||
|  | ||||
|         if (sec > hours) | ||||
|         { | ||||
|             strResout += (sec / hours) + "小时"; | ||||
|             sec %= hours; | ||||
|         } | ||||
|  | ||||
|         if (sec > minutes) | ||||
|         { | ||||
|             strResout += (sec / minutes) + "分"; | ||||
|             sec %= minutes; | ||||
|         } | ||||
|  | ||||
|         strResout += sec + "秒"; | ||||
|         return strResout; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// ToLong | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static long ToLong(this DateTime value, long defaultValue = 0) | ||||
|     { | ||||
|         // 特殊处理时间,转Unix毫秒 | ||||
|         if (value == DateTime.MinValue) return 0; | ||||
|  | ||||
|         //// 先转UTC时间再相减,以得到绝对时间差 | ||||
|         //return (Int32)(dt.ToUniversalTime() - _dt1970).TotalSeconds; | ||||
|         return (Int64)(value - _dt1970).TotalMilliseconds; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,111 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
| using Furion.Logging; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Core; | ||||
|  | ||||
| /// <summary> | ||||
| /// 日志写入文件的组件 | ||||
| /// </summary> | ||||
| public sealed class LoggingFileComponent : IServiceComponent | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public void Load(IServiceCollection services, ComponentContext componentContext) | ||||
|     { | ||||
|         var logFileEnable = App.GetConfig<bool?>("Logging:LogEnable:File"); | ||||
|         if (logFileEnable != true) return; | ||||
|  | ||||
|         //获取默认日志等级 | ||||
|         var defaultLevel = App.GetConfig<LogLevel?>("Logging:LogLevel:File"); | ||||
|         //获取程序根目录 | ||||
|         var rootPath = App.HostEnvironment.ContentRootPath; | ||||
|         if (defaultLevel != null)//如果默认日志等级不是空 | ||||
|         { | ||||
|             //遍历日志等级 | ||||
|             foreach (LogLevel level in Enum.GetValues(typeof(LogLevel))) | ||||
|             { | ||||
|                 //如果日志等级是默认等级和最大等级之间 | ||||
|                 if (level >= defaultLevel && level != LogLevel.None) | ||||
|                 { | ||||
|                     //每天创建一个日志文件 | ||||
|                     services.AddLogging(builder => | ||||
|                    { | ||||
|                        var fileName = "logs/" + level.ToString() + "/{0:yyyy}-{0:MM}-{0:dd}{0:zz}.log"; | ||||
|                        builder.AddFile(fileName, options => | ||||
|                        { | ||||
|                            SetLogOptions(options, level);//日志格式化 | ||||
|                        }); | ||||
|                    }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             //添加日志文件 | ||||
|             services.AddFileLogging("logs/{0:yyyy}-{0:MM}-{0:dd}{0:zz}.log", options => | ||||
|             { | ||||
|                 SetLogOptions(options, null);//日志格式化 | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志格式化 | ||||
|     /// </summary> | ||||
|     /// <param name="options"></param> | ||||
|     /// <param name="logLevel"></param> | ||||
|     private static void SetLogOptions(FileLoggerOptions options, LogLevel? logLevel) | ||||
|     { | ||||
|         //每天创建一个日志文件 | ||||
|         var rootPath = App.HostEnvironment.ContentRootPath; | ||||
|         if (logLevel != null)//如果日志等级不为空 | ||||
|         { | ||||
|             //过滤日志等级 | ||||
|             options.WriteFilter = (logMsg) => | ||||
|             { | ||||
|                 //不写入LoggingMonitor | ||||
|                 if (logMsg.LogName == "System.Logging.LoggingMonitor") | ||||
|                     return false; | ||||
|                 //只写入NetCore日志 | ||||
|                 if (!logMsg.LogName.StartsWith("System") && !logMsg.LogName.StartsWith("Microsoft")) | ||||
|                     return false; | ||||
|                 return logMsg.LogLevel == logLevel; | ||||
|             }; | ||||
|         } | ||||
|         //定义日志文件名 | ||||
|         options.FileNameRule = fileName => | ||||
|         { | ||||
|             return rootPath + "\\" + string.Format(fileName, SysDateTimeExtensions.CurrentDateTime); | ||||
|         }; | ||||
|         options.FileSizeLimitBytes = 50 * 1024 * 1024;//日志最大50M | ||||
|         options.MessageFormat = logMsg => | ||||
|             { | ||||
|                 var stringBuilder = new StringBuilder(); | ||||
|                 stringBuilder.AppendLine("【日志级别】:" + logMsg.LogLevel); | ||||
|                 stringBuilder.AppendLine("【日志类名】:" + logMsg.LogName); | ||||
|                 stringBuilder.AppendLine("【日志时间】:" + SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat()); | ||||
|                 stringBuilder.AppendLine("【日志内容】:" + logMsg.Message); | ||||
|                 if (logMsg.Exception != null) | ||||
|                 { | ||||
|                     stringBuilder.AppendLine("【异常信息】:" + logMsg.Exception); | ||||
|                 } | ||||
|                 return stringBuilder.ToString(); | ||||
|             }; | ||||
|     } | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.8.8.40" /> | ||||
| 		<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.40" /> | ||||
| 		<PackageReference Include="Furion.Pure" Version="4.8.8.40" /> | ||||
| 		<PackageReference Include="SqlSugarCore" Version="5.1.4.96" /> | ||||
| 		<PackageReference Include="UAParser" Version="3.1.47" /> | ||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||
| 		<PackageReference Include="MiniExcel" Version="1.31.2" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| </Project> | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,135 +0,0 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.6.33829.357 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Core", "ThingsGateway.Admin.Core\ThingsGateway.Admin.Core.csproj", "{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "script", "script", "{0BBD36E5-4FDB-4A9F-8387-6A39BEB6D87D}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		..\.gitattributes = ..\.gitattributes | ||||
| 		..\.gitignore = ..\.gitignore | ||||
| 		Directory.Build.props = Directory.Build.props | ||||
| 		..\LICENSE.txt = ..\LICENSE.txt | ||||
| 		..\README.md = ..\README.md | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Entry", "ThingsGateway.Web.Entry\ThingsGateway.Web.Entry.csproj", "{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "ThingsGateway.Web.Core\ThingsGateway.Web.Core.csproj", "{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor.Core", "ThingsGateway.Admin.Blazor.Core\ThingsGateway.Admin.Blazor.Core.csproj", "{096A0468-B4F0-490F-ABDF-78F66CCE2870}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Blazor", "ThingsGateway.Admin.Blazor\ThingsGateway.Admin.Blazor.csproj", "{6819B227-F69F-4478-819B-B95F170E11D5}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.Application", "ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj", "{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{07EEACCE-83E1-459F-89B8-C2DFD30AB86E}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		.editorconfig = .editorconfig | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Admin.ApiController", "ThingsGateway.Admin.ApiController\ThingsGateway.Admin.ApiController.csproj", "{516316AA-8891-4741-8D30-9565CEB64FEA}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Debug|x86 = Debug|x86 | ||||
| 		KINGVIEW|Any CPU = KINGVIEW|Any CPU | ||||
| 		KINGVIEW|x86 = KINGVIEW|x86 | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 		Release|x86 = Release|x86 | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|x86.ActiveCfg = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.KINGVIEW|x86.Build.0 = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{62FA6CF1-DB7E-4457-9EB7-C825A89751EC}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|x86.ActiveCfg = Debug|x86 | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Debug|x86.Build.0 = Debug|x86 | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|Any CPU.ActiveCfg = KINGVIEW|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|Any CPU.Build.0 = KINGVIEW|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|x86.ActiveCfg = KINGVIEW|x86 | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.KINGVIEW|x86.Build.0 = KINGVIEW|x86 | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|x86.ActiveCfg = Release|x86 | ||||
| 		{1B5B6C91-4A6C-4635-8B2C-D0F0957D3788}.Release|x86.Build.0 = Release|x86 | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.KINGVIEW|x86.Build.0 = Debug|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{E7DD3841-A7AD-4CC9-9122-E62C7976AA99}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.KINGVIEW|x86.Build.0 = Debug|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{096A0468-B4F0-490F-ABDF-78F66CCE2870}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.KINGVIEW|x86.Build.0 = Debug|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{6819B227-F69F-4478-819B-B95F170E11D5}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.KINGVIEW|x86.Build.0 = Debug|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{2C2BEC10-8B46-4892-8CA5-0FCF1477834E}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.KINGVIEW|x86.Build.0 = Debug|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{516316AA-8891-4741-8D30-9565CEB64FEA}.Release|x86.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {38B292E0-B3E7-4608-9912-1B057C2A508F} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
| @@ -1,15 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<OpenApiGenerateDocuments>true</OpenApiGenerateDocuments> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Application\ThingsGateway.Application.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| </Project> | ||||
| @@ -1,125 +0,0 @@ | ||||
| <?xml version="1.0"?> | ||||
| <doc> | ||||
|     <assembly> | ||||
|         <name>ThingsGateway.ApiController</name> | ||||
|     </assembly> | ||||
|     <members> | ||||
|         <member name="T:ThingsGateway.ApiController.CollectDbInfoControler"> | ||||
|             <summary> | ||||
|             采集设备 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.CollectDbInfoControler.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory,ThingsGateway.Application.IVariableService,ThingsGateway.Application.ICollectDeviceService)"> | ||||
|             <inheritdoc cref="T:ThingsGateway.ApiController.CollectDbInfoControler"/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.CollectDbInfoControler.GetCollectDeviceList(ThingsGateway.Application.CollectDevicePageInput)"> | ||||
|             <summary> | ||||
|             获取采集设备信息 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.CollectDbInfoControler.GetVariableList(ThingsGateway.Application.VariablePageInput)"> | ||||
|             <summary> | ||||
|             获取变量信息 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.ApiController.FileController"> | ||||
|             <summary> | ||||
|             文件下载 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.#ctor(ThingsGateway.Application.IRpcLogService,ThingsGateway.Application.IBackendLogService,ThingsGateway.Application.ICollectDeviceService,ThingsGateway.Application.IUploadDeviceService,ThingsGateway.Application.IVariableService)"> | ||||
|             <summary> | ||||
|             <inheritdoc cref="T:ThingsGateway.ApiController.FileController"/> | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.DownloadRpcLogAsync(ThingsGateway.Application.RpcLogInput)"> | ||||
|             <summary> | ||||
|             下载RPC日志 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.DownloadBackendLogAsync(ThingsGateway.Application.BackendLogInput)"> | ||||
|             <summary> | ||||
|             下载后台日志 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.DownloadCollectDeviceAsync(ThingsGateway.Application.CollectDeviceInput)"> | ||||
|             <summary> | ||||
|             导出采集设备 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.DownloadUploadDeviceAsync(ThingsGateway.Application.UploadDeviceInput)"> | ||||
|             <summary> | ||||
|             导出上传设备 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.DownloadDeviceVariableAsync(ThingsGateway.Application.DeviceVariableInput)"> | ||||
|             <summary> | ||||
|             导出采集变量 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.FileController.DownloadMemoryVariableAsync(ThingsGateway.Application.MemoryVariableInput)"> | ||||
|             <summary> | ||||
|             导出内存变量 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.ApiController.RpcControler"> | ||||
|             <summary> | ||||
|             设备控制 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.RpcControler.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)"> | ||||
|             <inheritdoc cref="T:ThingsGateway.ApiController.RpcControler"/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.RpcControler.ConfigDeviceThread(System.Int64,System.Boolean)"> | ||||
|             <summary> | ||||
|             控制采集线程启停 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.RpcControler.UpDeviceThread(System.Int64)"> | ||||
|             <summary> | ||||
|             重启采集线程 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.ApiController.RpcControler.WriteDeviceMethods(System.Collections.Generic.Dictionary{System.String,System.String})"> | ||||
|             <summary> | ||||
|             写入多个变量 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:ThingsGateway.Web.Foundation.CollectInfoControler"> | ||||
|             <summary> | ||||
|             采集状态信息 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)"> | ||||
|             <inheritdoc cref="T:ThingsGateway.Web.Foundation.CollectInfoControler"/> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.GetCollectDeviceList"> | ||||
|             <summary> | ||||
|             获取设备信息 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.GetDeviceVariableList(ThingsGateway.Application.VariablePageInput)"> | ||||
|             <summary> | ||||
|             获取变量信息 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|         <member name="M:ThingsGateway.Web.Foundation.CollectInfoControler.GetRealAlarmList(ThingsGateway.Application.VariablePageInput)"> | ||||
|             <summary> | ||||
|             获取实时报警信息 | ||||
|             </summary> | ||||
|             <returns></returns> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
| @@ -1,133 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Reflection; | ||||
|  | ||||
| using Yitter.IdGenerator; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 缓存帮助类 | ||||
| /// </summary> | ||||
| public class CacheDb | ||||
| { | ||||
|     private readonly string Id; | ||||
|     /// <summary> | ||||
|     /// 构造函数传入Id号作为Sqlite文件名称 | ||||
|     /// </summary> | ||||
|     /// <param name="id"></param> | ||||
|     public CacheDb(string id) | ||||
|     { | ||||
|         Id = id; | ||||
|         Directory.CreateDirectory(Path.Combine(AppContext.BaseDirectory, "CacheDb")); | ||||
|         if (!File.Exists(Path.Combine(AppContext.BaseDirectory, "CacheDb", $"{Id}.db"))) | ||||
|         { | ||||
|             GetCacheDb().DbMaintenance.CreateDatabase();//创建数据库 | ||||
|             GetCacheDb().CodeFirst.InitTables(typeof(CacheTable)); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 获取数据库链接 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private SqlSugarClient GetCacheDb() | ||||
|     { | ||||
|         var configureExternalServices = new ConfigureExternalServices | ||||
|         { | ||||
|             EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required | ||||
|             { | ||||
|                 if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) | ||||
|                     || (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null)) | ||||
|                     column.IsNullable = true; | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
|         var sqlSugarClient = new SqlSugarClient(new ConnectionConfig() | ||||
|         { | ||||
|             ConnectionString = $"Data Source=CacheDb/{Id}.db;",//连接字符串 | ||||
|             DbType = DbType.Sqlite,//数据库类型 | ||||
|             IsAutoCloseConnection = true, //不设成true要手动close | ||||
|             ConfigureExternalServices = configureExternalServices, | ||||
|         } | ||||
|         ); | ||||
|         return sqlSugarClient; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取缓存表前n条 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<List<CacheTable>> GetCacheData(int take) | ||||
|     { | ||||
|         var db = GetCacheDb(); | ||||
|         var data = await db.Queryable<CacheTable>().Take(take).ToListAsync(); | ||||
|         return data; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 获取缓存表全部 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<List<CacheTable>> GetCacheData() | ||||
|     { | ||||
|         var db = GetCacheDb(); | ||||
|         var data = await db.Queryable<CacheTable>().ToListAsync(); | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 增加离线缓存,限制表最大默认2000行 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<bool> AddCacheData(string topic, string data, int max = 2000) | ||||
|     { | ||||
|         var db = GetCacheDb(); | ||||
|         var count = await db.Queryable<CacheTable>().CountAsync(); | ||||
|         if (count > max) | ||||
|         { | ||||
|             var data1 = await db.Queryable<CacheTable>().OrderBy(a => a.Id).Take(count - max).ToListAsync(); | ||||
|             await db.Deleteable(data1).ExecuteCommandAsync(); | ||||
|         } | ||||
|         var result = await db.Insertable(new CacheTable() { Id = YitIdHelper.NextId(), Topic = topic, CacheStr = data }).ExecuteCommandAsync(); | ||||
|         return result > 0; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 清除离线缓存 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<bool> DeleteCacheData(params long[] data) | ||||
|     { | ||||
|         var db = GetCacheDb(); | ||||
|         var result = await db.Deleteable<CacheTable>().In(data).ExecuteCommandAsync(); | ||||
|         return result > 0; | ||||
|     } | ||||
| } | ||||
| /// <summary> | ||||
| /// 缓存表 | ||||
| /// </summary> | ||||
| public class CacheTable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Id | ||||
|     /// </summary> | ||||
|     [SugarColumn(IsPrimaryKey = true)] | ||||
|     public long Id { get; set; } | ||||
|     /// <summary> | ||||
|     /// Topic | ||||
|     /// </summary> | ||||
|     public string Topic { get; set; } | ||||
|     /// <summary> | ||||
|     /// 缓存值 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)] | ||||
|     public string CacheStr { get; set; } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 采集设备表 | ||||
| /// </summary> | ||||
| [SugarTable("collectDevice", TableDescription = "采集设备表")] | ||||
| [Tenant(ThingsGatewayConst.DB_ThingsGateway)] | ||||
| [SugarIndex("unique_collectdevice_name", nameof(CollectDevice.Name), OrderByType.Asc, true)] | ||||
| public class CollectDevice : UploadDevice | ||||
| { | ||||
|     #region 冗余配置 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否冗余 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "IsRedundant", ColumnDescription = "是否冗余")] | ||||
|     [DataTable(Order = 9, IsShow = true, Sortable = true)] | ||||
|     public bool IsRedundant { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 冗余设备Id,只能选择相同驱动 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "RedundantDeviceId", ColumnDescription = "冗余设备Id")] | ||||
|     [IgnoreExcel] | ||||
|     public long RedundantDeviceId { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,71 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 设备变量表 | ||||
| /// </summary> | ||||
| [SugarTable("deviceVariable", TableDescription = "设备变量表")] | ||||
| [Tenant(ThingsGatewayConst.DB_ThingsGateway)] | ||||
| [SugarIndex("index_device", nameof(DeviceVariable.DeviceId), OrderByType.Asc)] | ||||
| [SugarIndex("unique_deviceVariable_name", nameof(DeviceVariable.Name), OrderByType.Asc, true)] | ||||
| public class DeviceVariable : MemoryVariable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 设备 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "DeviceId", ColumnDescription = "设备")] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     [IgnoreExcel] | ||||
|     public virtual long DeviceId { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 单位 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "Unit", ColumnDescription = "单位", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     public string Unit { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行间隔 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "InvokeInterval", ColumnDescription = "执行间隔")] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     public virtual int IntervalTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 其他方法,若不为空,此时Address为方法参数 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "OtherMethod", ColumnDescription = "特殊方法", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 7, IsShow = true, Sortable = true)] | ||||
|  | ||||
|     public string OtherMethod { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 变量地址,可能带有额外的信息,比如<see cref="DataFormat"/> ,以;分割 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "VariableAddress", ColumnDescription = "变量地址", Length = 200, IsNullable = true)] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     public string VariableAddress { get; set; } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否中间变量 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "IsMemoryVariable", ColumnDescription = "是否中间变量", IsNullable = false)] | ||||
|     [IgnoreExcel] | ||||
|     public override bool IsMemoryVariable { get; set; } = false; | ||||
| } | ||||
|  | ||||
| @@ -1,52 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using SqlSugar.DbConvert; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 插件信息表 | ||||
| /// </summary> | ||||
| [SugarTable("driverPlugin", TableDescription = "插件信息表")] | ||||
| [Tenant(ThingsGatewayConst.DB_ThingsGateway)] | ||||
| [SugarIndex("unique_driverplugin_name", nameof(DriverPlugin.AssembleName), OrderByType.Asc, true)] | ||||
| public class DriverPlugin : BaseEntity | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 文件名称 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "FileName", ColumnDescription = "文件名称")] | ||||
|     [DataTable(Order = 1, IsShow = true, Sortable = true)] | ||||
|     public string FileName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件类名称 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "AssembleName", ColumnDescription = "插件类名称")] | ||||
|     [DataTable(Order = 2, IsShow = true, Sortable = true)] | ||||
|     public string AssembleName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件类型 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnDataType = "varchar(50)", ColumnName = "DriverTypeEnum", ColumnDescription = "插件类型", SqlParameterDbType = typeof(EnumToStringConvert))] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = true)] | ||||
|     public DriverEnum DriverTypeEnum { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件文件全路径 | ||||
|     /// </summary> | ||||
|     [SugarColumn(ColumnName = "FilePath", ColumnDescription = "插件文件全路径")] | ||||
|     [DataTable(Order = 4, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")] | ||||
|     public string FilePath { get; set; } | ||||
|  | ||||
| } | ||||
| @@ -1,186 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using Hardware.Info; | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Net.Http; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Text; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 硬件信息获取 | ||||
| /// </summary> | ||||
| public class HardwareInfoService : ISingleton | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 硬件信息获取 | ||||
|     /// </summary> | ||||
|     public HardwareInfo HardwareInfo { get; private set; } | ||||
|  | ||||
|     private readonly ILogger _logger; | ||||
|  | ||||
|     /// <inheritdoc cref="HardwareInfoService"/> | ||||
|     public HardwareInfoService(ILogger<HardwareInfoService> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 循环获取 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public void Init() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             HardwareInfo = new(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "初始化硬件信息失败"); | ||||
|         } | ||||
|         Task.Factory.StartNew(async () => | ||||
|        { | ||||
|            string currentPath = Directory.GetCurrentDirectory(); | ||||
|            DriveInfo drive = new(Path.GetPathRoot(currentPath)); | ||||
|            appInfo = new() | ||||
|            { | ||||
|                DriveInfo = drive, | ||||
|                HostName = Environment.MachineName, // 主机名称 | ||||
|                SystemOs = RuntimeInformation.OSDescription, // 操作系统 | ||||
|                OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构 | ||||
|                RemoteIp = await GetIpFromOnlineAsync(), // 外网地址 | ||||
|                FrameworkDescription = RuntimeInformation.FrameworkDescription, // NET框架 | ||||
|                Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production", | ||||
|                Stage = App.HostEnvironment.IsStaging() ? "Stage" : "非Stage", // 是否Stage环境 | ||||
|                UpdateTime = SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(), | ||||
|            }; | ||||
|            while (true) | ||||
|            { | ||||
|                try | ||||
|                { | ||||
|                    appInfo.UpdateTime = SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(); | ||||
|                    appInfo.RemoteIp = await GetIpFromOnlineAsync(); | ||||
|  | ||||
|                    HardwareInfo?.RefreshMemoryStatus(); | ||||
|                    HardwareInfo?.RefreshMemoryList(); | ||||
|                    HardwareInfo?.RefreshNetworkAdapterList(); | ||||
|                    HardwareInfo?.RefreshCPUList(); | ||||
|                    //10秒更新一次 | ||||
|                    await Task.Delay(10000); | ||||
|                } | ||||
|                catch (Exception ex) | ||||
|                { | ||||
|                    _logger.LogWarning(ex, "获取硬件信息失败"); | ||||
|                } | ||||
|            } | ||||
|        } | ||||
|        , TaskCreationOptions.LongRunning); | ||||
|  | ||||
|     } | ||||
|     private TGAPPInfo appInfo = new(); | ||||
|     /// <summary> | ||||
|     /// 运行信息获取 | ||||
|     /// </summary> | ||||
|     public TGAPPInfo APPInfo => appInfo; | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// IP地址信息 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<string> GetIpFromOnlineAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var url = "http://myip.ipip.net"; | ||||
|             var stream = await new HttpClient().GetStreamAsync(url); | ||||
|             var streamReader = new StreamReader(stream, Encoding.UTF8); | ||||
|             var html = streamReader.ReadToEnd(); | ||||
|             return html.Replace("当前 IP:", "").Replace("来自于:", ""); | ||||
|         } | ||||
|         catch (Exception) | ||||
|         { | ||||
|             return ""; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public class TGAPPInfo | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 主机环境 | ||||
|     /// </summary> | ||||
|     [Description("主机环境")] | ||||
|     public string Environment { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// NET框架 | ||||
|     /// </summary> | ||||
|     [Description("NET框架")] | ||||
|     public string FrameworkDescription { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 主机名称 | ||||
|     /// </summary> | ||||
|     [Description("主机名称")] | ||||
|     public string HostName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 系统架构 | ||||
|     /// </summary> | ||||
|     [Description("系统架构")] | ||||
|     public string OsArchitecture { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 外网地址 | ||||
|     /// </summary> | ||||
|     [Description("外网地址")] | ||||
|     public string RemoteIp { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Stage环境 | ||||
|     /// </summary> | ||||
|     [Description("Stage环境")] | ||||
|     public string Stage { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 操作系统 | ||||
|     /// </summary> | ||||
|     [Description("操作系统")] | ||||
|     public string SystemOs { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 更新时间 | ||||
|     /// </summary> | ||||
|     [Description("更新时间")] | ||||
|     public string UpdateTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前磁盘信息 | ||||
|     /// </summary> | ||||
|     [Description("当前磁盘信息")] | ||||
|     public DriveInfo DriveInfo { get; set; } | ||||
| } | ||||
|  | ||||
| @@ -1,207 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using System.Net; | ||||
| using System.Reflection; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 读写扩展 | ||||
| /// </summary> | ||||
| public static class ReadWriteHelpers | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 根据数据类型写入设备,只支持C#内置数据类型,但不包含<see cref="decimal"/>和<see cref="char"/>和<see cref="sbyte"/> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static object GetObjectData(this string value) | ||||
|     { | ||||
|         //判断数值类型 | ||||
|         Regex regex = new("^[-+]?[0-9]*\\.?[0-9]+$"); | ||||
|         bool match = regex.IsMatch(value); | ||||
|         if (match) | ||||
|         { | ||||
|             if (value.ToDecimal() == 0 && Convert.ToInt64(value) != 0) | ||||
|             { | ||||
|                 throw new("转换失败"); | ||||
|             } | ||||
|             return value.ToDecimal(); | ||||
|         } | ||||
|         else if (value.IsBoolValue()) | ||||
|         { | ||||
|             return value.GetBoolValue(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return value; | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 根据<see cref="OperResult.IsSuccess"/>执行action | ||||
|     /// </summary> | ||||
|     public static OperResult<T> DealWithReadResult<T>(OperResult<T> read, Action<T> action) | ||||
|     { | ||||
|         if (!read.IsSuccess || action == null) | ||||
|             return read; | ||||
|         action(read.Content); | ||||
|         return read; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 根据<see cref="PropertyInfo"/> 数据类型转化返回值类型 | ||||
|     /// </summary> | ||||
|     /// <param name="p"></param> | ||||
|     /// <param name="value"></param> | ||||
|     /// <returns></returns> | ||||
|     public static object ObjToTypeValue(this PropertyInfo p, string value) | ||||
|     { | ||||
|         object _value = null; | ||||
|         if (p.PropertyType == typeof(bool)) | ||||
|             _value = value.GetBoolValue(); | ||||
|         else if (p.PropertyType == typeof(byte)) | ||||
|             _value = byte.Parse(value); | ||||
|         else if (p.PropertyType == typeof(sbyte)) | ||||
|             _value = sbyte.Parse(value); | ||||
|         else if (p.PropertyType == typeof(short)) | ||||
|             _value = short.Parse(value); | ||||
|         else if (p.PropertyType == typeof(ushort)) | ||||
|             _value = ushort.Parse(value); | ||||
|         else if (p.PropertyType == typeof(int)) | ||||
|             _value = int.Parse(value); | ||||
|         else if (p.PropertyType == typeof(uint)) | ||||
|             _value = uint.Parse(value); | ||||
|         else if (p.PropertyType == typeof(long)) | ||||
|             _value = long.Parse(value); | ||||
|         else if (p.PropertyType == typeof(ulong)) | ||||
|             _value = ulong.Parse(value); | ||||
|         else if (p.PropertyType == typeof(float)) | ||||
|             _value = float.Parse(value); | ||||
|         else if (p.PropertyType == typeof(double)) | ||||
|             _value = double.Parse(value); | ||||
|         else if (p.PropertyType == typeof(decimal)) | ||||
|             _value = decimal.Parse(value); | ||||
|         else if (p.PropertyType == typeof(DateTime)) | ||||
|             _value = DateTime.Parse(value); | ||||
|         else if (p.PropertyType == typeof(DateTimeOffset)) | ||||
|             _value = DateTimeOffset.Parse(value); | ||||
|         else if (p.PropertyType == typeof(string)) | ||||
|             _value = value; | ||||
|         else if (p.PropertyType == typeof(IPAddress)) | ||||
|             _value = IPAddress.Parse(value); | ||||
|         else if (p.PropertyType.IsEnum) | ||||
|             _value = Enum.Parse(p.PropertyType, value); | ||||
|         return _value; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 在返回的字节数组中解析每个变量的值 | ||||
|     /// 根据每个变量的<see cref="DeviceVariableRunTime.Index"/> | ||||
|     /// 不支持变长字符串类型变量,一定不能存在于变量List中 | ||||
|     /// </summary> | ||||
|     /// <param name="buffer">返回的字节数组</param> | ||||
|     /// <param name="values">设备变量List</param> | ||||
|     /// <param name="startIndex">开始序号</param> | ||||
|     public static void PraseStructContent(byte[] buffer, IList<DeviceVariableRunTime> values, int startIndex = 0) | ||||
|     { | ||||
|         foreach (DeviceVariableRunTime organizedVariable in values) | ||||
|         { | ||||
|             var deviceValue = organizedVariable; | ||||
|             IThingsGatewayBitConverter byteConverter = deviceValue.ThingsGatewayBitConverter; | ||||
|             Type propertyType = organizedVariable.DataType; | ||||
|             int index = organizedVariable.Index; | ||||
|             if (propertyType == typeof(bool)) | ||||
|             { | ||||
|                 if (index + startIndex > buffer.Length * 8) | ||||
|                     throw new Exception($"返回数据长度{buffer.Length}不足以转换为对应索引位{index + startIndex}的数据,请检查数据类型"); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (index + startIndex > buffer.Length) | ||||
|                     throw new Exception($"返回数据长度{buffer.Length}不足以转换为对应索引位{index + startIndex}的数据,请检查数据类型"); | ||||
|             } | ||||
|  | ||||
|             if (propertyType == typeof(byte)) | ||||
|             { | ||||
|                 byte num = byteConverter.ToByte(buffer, index + startIndex); | ||||
|  | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(short)) | ||||
|             { | ||||
|                 short num = byteConverter.ToInt16(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(ushort)) | ||||
|             { | ||||
|                 ushort num = byteConverter.ToUInt16(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(int)) | ||||
|             { | ||||
|                 int num = byteConverter.ToInt32(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(uint)) | ||||
|             { | ||||
|                 uint num = byteConverter.ToUInt32(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(long)) | ||||
|             { | ||||
|                 long num = byteConverter.ToInt64(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(ulong)) | ||||
|             { | ||||
|                 ulong num = byteConverter.ToUInt64(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(float)) | ||||
|             { | ||||
|                 float num = byteConverter.ToSingle(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(double)) | ||||
|             { | ||||
|                 double num = byteConverter.ToDouble(buffer, index + startIndex); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(bool)) | ||||
|             { | ||||
|                 bool num = byteConverter.ToBoolean(buffer, index + (startIndex * 8)); | ||||
|                 Set(organizedVariable, num); | ||||
|             } | ||||
|             else if (propertyType == typeof(string)) | ||||
|             { | ||||
|                 string num = byteConverter.ToString(buffer, index + startIndex, byteConverter.StringLength); | ||||
|                 Set(organizedVariable, num); | ||||
|  | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         static void Set(DeviceVariableRunTime organizedVariable, object num) | ||||
|         { | ||||
|             var operResult = organizedVariable.SetValue(num); ; | ||||
|             if (!operResult.IsSuccess) | ||||
|             { | ||||
|                 throw new Exception(operResult.Message); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -1,126 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using System.ComponentModel; | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 上传设备运行状态 | ||||
| /// </summary> | ||||
| public class UploadDeviceRunTime : UploadDevice | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 设备驱动名称 | ||||
|     /// </summary> | ||||
|     [Description("设备驱动名称")] | ||||
|     public string PluginName { get; set; } | ||||
|     /// <summary> | ||||
|     /// 关联变量数量 | ||||
|     /// </summary> | ||||
|     [Description("关联变量数量")] | ||||
|     public int UploadVariableCount { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备活跃时间 | ||||
|     /// </summary> | ||||
|     [Description("活跃时间")] | ||||
|     public DateTime ActiveTime { get; private set; } = DateTime.MinValue; | ||||
|     /// <summary> | ||||
|     /// 设备状态 | ||||
|     /// </summary> | ||||
|     [Description("设备状态")] | ||||
|     public DeviceStatusEnum DeviceStatus | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             if (KeepRun) | ||||
|                 return deviceStatus; | ||||
|             else | ||||
|                 return DeviceStatusEnum.Pause; | ||||
|         } | ||||
|         private set | ||||
|         { | ||||
|             if (deviceStatus != value) | ||||
|             { | ||||
|                 deviceStatus = value; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     private string lastErrorMessage; | ||||
|     /// <summary> | ||||
|     /// 最后一次失败原因 | ||||
|     /// </summary> | ||||
|     [Description("最后一次失败原因")] | ||||
|     public string LastErrorMessage | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             return lastErrorMessage; | ||||
|         } | ||||
|         private set | ||||
|         { | ||||
|             lastErrorMessage = SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat() + " - " + value; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 运行 | ||||
|     /// </summary> | ||||
|     [Description("运行")] | ||||
|     public bool KeepRun { get; set; } = true; | ||||
|  | ||||
|     private int errorCount; | ||||
|     /// <summary> | ||||
|     /// 距上次成功时的读取失败次数,超过3次设备更新为离线,等于0时设备更新为在线 | ||||
|     /// </summary> | ||||
|     [Description("失败次数")] | ||||
|     public int ErrorCount | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             return errorCount; | ||||
|         } | ||||
|         private set | ||||
|         { | ||||
|             errorCount = value; | ||||
|             if (errorCount > 3) | ||||
|             { | ||||
|                 DeviceStatus = DeviceStatusEnum.OffLine; | ||||
|             } | ||||
|             else if (errorCount == 0) | ||||
|             { | ||||
|                 DeviceStatus = DeviceStatusEnum.OnLine; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private DeviceStatusEnum deviceStatus = DeviceStatusEnum.None; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 传入设备的状态信息 | ||||
|     /// </summary> | ||||
|     /// <param name="activeTime"></param> | ||||
|     /// <param name="errorCount"></param> | ||||
|     /// <param name="lastErrorMessage"></param> | ||||
|     public void SetDeviceStatus(DateTime? activeTime = null, int? errorCount = null, string lastErrorMessage = null) | ||||
|     { | ||||
|         if (activeTime != null) | ||||
|             ActiveTime = activeTime.Value; | ||||
|         if (errorCount != null) | ||||
|             ErrorCount = errorCount.Value; | ||||
|         if (lastErrorMessage != null) | ||||
|             LastErrorMessage = lastErrorMessage; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,213 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.Foundation.Serial; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// <para></para> | ||||
| /// 采集插件,继承实现不同PLC通讯 | ||||
| /// <para></para> | ||||
| /// 读取字符串,DateTime等等不确定返回字节数量的方法属性特殊方法,需使用<see cref="DeviceMethodAttribute"/>特性标识 | ||||
| /// </summary> | ||||
| public abstract class CollectBase : DriverBase | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 当前采集设备 | ||||
|     /// </summary> | ||||
|     public CollectDeviceRunTime CurDevice; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回是否支持读取 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract bool IsSupportRequest { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 数据转换器 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract IThingsGatewayBitConverter ThingsGatewayBitConverter { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 结束通讯后执行的方法 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract Task AfterStopAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始通讯前执行的方法 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract Task BeforStartAsync(CancellationToken token); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通道标识 | ||||
|     /// </summary> | ||||
|     public virtual OperResult<string> GetChannelID() | ||||
|     { | ||||
|         var config = (CollectDriverPropertyBase)DriverPropertys; | ||||
|         if (config.IsShareChannel) | ||||
|         { | ||||
|             switch (config.ShareChannel) | ||||
|             { | ||||
|                 case ShareChannelEnum.SerialPort: | ||||
|                     return OperResult.CreateSuccessResult(config.PortName); | ||||
|                 case ShareChannelEnum.TcpClientEx: | ||||
|                 case ShareChannelEnum.UdpSession: | ||||
|                     var a = new IPHost($"{config.IP}:{config.Port}"); | ||||
|                     return OperResult.CreateSuccessResult(config.ShareChannel.ToString() + a.ToString()); | ||||
|             } | ||||
|         } | ||||
|         return new("不支持共享通道"); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 共享通道类型 | ||||
|     /// </summary> | ||||
|     public virtual OperResult<object> GetShareChannel() | ||||
|     { | ||||
|         var config = (CollectDriverPropertyBase)DriverPropertys; | ||||
|         if (config.IsShareChannel) | ||||
|         { | ||||
|             switch (config.ShareChannel) | ||||
|             { | ||||
|                 case ShareChannelEnum.None: | ||||
|                     return new OperResult<object>("不支持共享链路"); | ||||
|                 case ShareChannelEnum.SerialPort: | ||||
|                     var data = new SerialProperty() | ||||
|                     { | ||||
|                         PortName = config.PortName, | ||||
|                         BaudRate = config.BaudRate, | ||||
|                         DataBits = config.DataBits, | ||||
|                         Parity = config.Parity, | ||||
|                         StopBits = config.StopBits, | ||||
|                     }; | ||||
|                     FoundataionConfig.SetValue(SerialConfigExtension.SerialProperty, data); | ||||
|                     var serialClient = new SerialClient(); | ||||
|                     (serialClient).Setup(FoundataionConfig); | ||||
|                     return OperResult.CreateSuccessResult((object)serialClient); | ||||
|                 case ShareChannelEnum.TcpClientEx: | ||||
|                     FoundataionConfig.SetRemoteIPHost(new IPHost($"{config.IP}:{config.Port}")); | ||||
|                     var tcpClient = new TcpClientEx(); | ||||
|                     (tcpClient).Setup(FoundataionConfig); | ||||
|                     return OperResult.CreateSuccessResult((object)tcpClient); | ||||
|                 case ShareChannelEnum.UdpSession: | ||||
|                     FoundataionConfig.SetRemoteIPHost(new IPHost($"{config.IP}:{config.Port}")); | ||||
|                     var udpSession = new UdpSession(); | ||||
|                     return OperResult.CreateSuccessResult((object)udpSession); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         return new OperResult<object>("不支持共享链路"); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     public void Init(ILogger logger, CollectDeviceRunTime device, object client = null) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         IsLogOut = device.IsLogOut; | ||||
|         CurDevice = device; | ||||
|         Init(device, client); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 共享链路需重新设置适配器时调用该方法 | ||||
|     /// </summary> | ||||
|     public abstract void InitDataAdapter(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 连读打包,返回实际通讯包信息<see cref="DeviceVariableSourceRead"/>  | ||||
|     /// <br></br>每个驱动打包方法不一样,所以需要实现这个接口 | ||||
|     /// </summary> | ||||
|     /// <param name="deviceVariables">设备下的全部通讯点位</param> | ||||
|     /// <returns></returns> | ||||
|     public abstract List<DeviceVariableSourceRead> LoadSourceRead(List<DeviceVariableRunTime> deviceVariables); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 采集驱动读取 | ||||
|     /// </summary> | ||||
|     public virtual async Task<OperResult<byte[]>> ReadSourceAsync(DeviceVariableSourceRead deviceVariableSourceRead, CancellationToken token) | ||||
|     { | ||||
|         if (IsSupportRequest) | ||||
|         { | ||||
|             OperResult<byte[]> read = await ReadAsync(deviceVariableSourceRead.Address, deviceVariableSourceRead.Length, token); | ||||
|             if (read == null || !read.IsSuccess) | ||||
|             { | ||||
|                 deviceVariableSourceRead.DeviceVariables.ForEach(it => | ||||
|                 { | ||||
|                     var operResult = it.SetValue(null); | ||||
|                     if (!operResult.IsSuccess) | ||||
|                     { | ||||
|                         _logger.LogWarning("变量值更新失败:" + operResult.Message); | ||||
|                     } | ||||
|                 }); | ||||
|                 return read; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return ReadWriteHelpers.DealWithReadResult(read, content => | ||||
|                 { | ||||
|                     ReadWriteHelpers.PraseStructContent(content, deviceVariableSourceRead.DeviceVariables); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return new OperResult<byte[]>("不支持默认读取方式"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 写入变量值 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract Task<OperResult> WriteValueAsync(DeviceVariableRunTime deviceVariable, JToken value, CancellationToken token); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     /// <param name="device">设备</param> | ||||
|     /// <param name="client">链路对象,如TCPClient</param> | ||||
|     protected abstract void Init(CollectDeviceRunTime device, object client = null); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 底层日志输出 | ||||
|     /// </summary> | ||||
|     protected override void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4) | ||||
|     { | ||||
|         if (arg1 >= TouchSocket.Core.LogLevel.Warning) | ||||
|         { | ||||
|             CurDevice.SetDeviceStatus(lastErrorMessage: arg3); | ||||
|         } | ||||
|         if (IsLogOut || arg1 >= TouchSocket.Core.LogLevel.Warning) | ||||
|         { | ||||
|             _logger.Log_Out(arg1, arg2, arg3, arg4); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回全部内容字节数组 | ||||
|     /// <br></br> | ||||
|     /// 通常使用<see cref="IReadWrite.ReadAsync(string, int, CancellationToken)"/>可以直接返回正确信息 | ||||
|     /// </summary> | ||||
|     protected abstract Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token); | ||||
| } | ||||
| @@ -1,116 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 插件基类,注意继承的插件的命名空间需要符合<see cref="ExportHelpers.PluginLeftName"/>前置名称 | ||||
| /// </summary> | ||||
| public abstract class DriverBase : DisposableObject | ||||
| { | ||||
|     /// <summary> | ||||
|     /// <inheritdoc cref="TouchSocket.Core.TouchSocketConfig"/> | ||||
|     /// </summary> | ||||
|     public TouchSocketConfig FoundataionConfig; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志 | ||||
|     /// </summary> | ||||
|     internal ILogger _logger; | ||||
|  | ||||
|     /// <inheritdoc cref="DriverBase"/> | ||||
|     public DriverBase() | ||||
|     { | ||||
|         FoundataionConfig = new TouchSocketConfig(); | ||||
|         LogMessage = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         FoundataionConfig.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     { | ||||
|         FoundataionConfig.Dispose(); | ||||
|         base.Dispose(disposing); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 调试UI Type,如果不存在,返回null | ||||
|     /// </summary> | ||||
|     public abstract Type DriverDebugUIType { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前插件描述 | ||||
|     /// </summary> | ||||
|     public DriverPlugin DriverPlugin { get; internal set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件配置项 ,继承实现<see cref="DriverPropertyBase"/>后,返回继承类,如果不存在,返回null | ||||
|     /// </summary> | ||||
|     public abstract DriverPropertyBase DriverPropertys { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否输出日志 | ||||
|     /// </summary> | ||||
|     public bool IsLogOut { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 报文信息 | ||||
|     /// </summary> | ||||
|     public ConcurrentLinkedList<string> Messages { get; set; } = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 底层日志,如果需要在Blazor界面中显示报文日志,需要输出字符串头部为<see cref="FoundationConst.LogMessageHeader"/>的日志 | ||||
|     /// </summary> | ||||
|     protected internal LoggerGroup LogMessage { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否连接成功,如果是上传设备,会直接影响到上传设备的运行状态,如果是采集设备并且不支持读取,需要自更新在线状态 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract bool IsConnected(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备报文 | ||||
|     /// </summary> | ||||
|     internal void NewMessage(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4) | ||||
|     { | ||||
|         if (IsLogOut) | ||||
|         { | ||||
|             if (arg3.StartsWith(FoundationConst.LogMessageHeader)) | ||||
|             { | ||||
|                 Messages.Add(SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat() + " - " + arg3.Substring(0, Math.Min(arg3.Length, 200))); | ||||
|  | ||||
|                 if (Messages.Count > 2500) | ||||
|                 { | ||||
|                     Messages.Clear(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 底层日志输出 | ||||
|     /// </summary> | ||||
|     protected virtual void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4) | ||||
|     { | ||||
|         if (IsLogOut || arg1 >= TouchSocket.Core.LogLevel.Warning) | ||||
|         { | ||||
|             _logger.Log_Out(arg1, arg2, arg3, arg4); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,324 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
| using Furion.FriendlyException; | ||||
|  | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
| using System.Runtime.Loader; | ||||
|  | ||||
| using ThingsGateway.Application.Extensions; | ||||
|  | ||||
| using Yitter.IdGenerator; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 驱动插件服务   | ||||
| /// </summary> | ||||
| public class PluginSingletonService : ISingleton | ||||
| { | ||||
|  | ||||
|     private readonly ILogger<PluginSingletonService> _logger; | ||||
|     /// <inheritdoc cref="PluginSingletonService"/> | ||||
|     public PluginSingletonService(ILogger<PluginSingletonService> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件文件路径/插件程序集 | ||||
|     /// </summary> | ||||
|     public ConcurrentDictionary<string, Assembly> AssemblyDict { get; private set; } = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件文件路径/插件域 | ||||
|     /// </summary> | ||||
|     public ConcurrentDictionary<string, AssemblyLoadContext> AssemblyLoadContextDict { get; private set; } = new(); | ||||
|     /// <summary> | ||||
|     /// 插件ID/插件Type | ||||
|     /// </summary> | ||||
|     public ConcurrentDictionary<long, Type> DriverPluginDict { get; private set; } = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取插件 | ||||
|     /// </summary> | ||||
|     /// <param name="plugin"></param> | ||||
|     /// <returns></returns> | ||||
|     public DriverBase GetDriver(DriverPlugin plugin) | ||||
|     { | ||||
|         lock (this) | ||||
|         { | ||||
|             //先判断是否已经拥有插件模块 | ||||
|             if (DriverPluginDict.ContainsKey(plugin.Id)) | ||||
|             { | ||||
|                 var driver = (DriverBase)Activator.CreateInstance(DriverPluginDict[plugin.Id]); | ||||
|                 driver.DriverPlugin = plugin; | ||||
|                 return driver; | ||||
|             } | ||||
|             Assembly assembly = null; | ||||
|  | ||||
|             _logger?.LogInformation($"添加插件文件:{plugin.FilePath}"); | ||||
|  | ||||
|             //根据路径获取dll文件 | ||||
|  | ||||
|             //主程序集路径 | ||||
|             var path = AppContext.BaseDirectory.CombinePathOS(plugin.FilePath); | ||||
|             //全部程序集路径 | ||||
|             List<string> paths = new(); | ||||
|             Directory.GetFiles(Path.GetDirectoryName(path), "*.dll").ToList(). | ||||
|                 ForEach(a => paths.Add(a.Replace("\\", "/"))); | ||||
|  | ||||
|             if (AssemblyDict.ContainsKey(plugin.FilePath)) | ||||
|             { | ||||
|                 assembly = AssemblyDict[plugin.FilePath]; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 //新建插件域,并注明不可卸载 | ||||
|                 var assemblyLoadContext = new AssemblyLoadContext(plugin.Id.ToString(), false); | ||||
|                 //获取插件程序集 | ||||
|                 assembly = GetAssembly(path, paths, assemblyLoadContext); | ||||
|                 //添加到全局对象 | ||||
|                 AssemblyLoadContextDict.TryAdd(plugin.FilePath, assemblyLoadContext); | ||||
|                 AssemblyDict.TryAdd(plugin.FilePath, assembly); | ||||
|             } | ||||
|  | ||||
|             if (assembly != null) | ||||
|             { | ||||
|                 //根据采集/上传类型获取实际插件类 | ||||
|                 switch (plugin.DriverTypeEnum) | ||||
|                 { | ||||
|                     case DriverEnum.Collect: | ||||
|                         var driverType = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName); | ||||
|                         if (driverType != null) | ||||
|                         { | ||||
|                             return GetDriver(plugin, driverType); | ||||
|                         } | ||||
|                         break; | ||||
|                     case DriverEnum.Upload: | ||||
|                         var upLoadType = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).FirstOrDefault(it => it.Name == plugin.AssembleName); | ||||
|                         if (upLoadType != null) | ||||
|                         { | ||||
|                             return GetDriver(plugin, upLoadType); | ||||
|                         } | ||||
|                         break; | ||||
|                 } | ||||
|  | ||||
|                 throw new Exception($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 失败,{plugin.AssembleName}不存在"); | ||||
|  | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 throw new Exception($"加载驱动插件 {path} 失败,文件不存在"); | ||||
|             } | ||||
|             DriverBase GetDriver(DriverPlugin plugin, Type driverType) | ||||
|             { | ||||
|                 var driver = (DriverBase)Activator.CreateInstance(driverType); | ||||
|                 _logger?.LogInformation($"加载插件 {plugin.FilePath}-{plugin.AssembleName} 成功"); | ||||
|                 DriverPluginDict.TryAdd(plugin.Id, driverType); | ||||
|                 driver.DriverPlugin = plugin; | ||||
|                 return driver; | ||||
|             } | ||||
|             Assembly GetAssembly(string path, List<string> paths, AssemblyLoadContext assemblyLoadContext) | ||||
|             { | ||||
|                 Assembly assembly = null; | ||||
|                 foreach (var item in paths) | ||||
|                 { | ||||
|                     using var fs = new FileStream(item, FileMode.Open); | ||||
|                     if (item == path) | ||||
|                         assembly = assemblyLoadContext.LoadFromStream(fs); | ||||
|                     else | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             assemblyLoadContext.LoadFromStream(fs); | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             _logger.LogWarning($"尝试加载附属程序集{item}失败,如果此程序集为非引用,比如非托管DllImport,可以忽略此警告。错误信息:{(ex.Message)}"); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return assembly; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取插件的属性值 | ||||
|     /// </summary> | ||||
|     public List<DependencyProperty> GetDriverProperties(DriverBase driver) | ||||
|     { | ||||
|         var data = driver.DriverPropertys?.GetType().GetPropertiesWithCache().SelectMany(it => | ||||
|             new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute<DevicePropertyAttribute>() } }) | ||||
|             .Where(x => x.devicePropertyAttribute != null).ToList() | ||||
|               .SelectMany(it => new[] | ||||
|               { | ||||
|                   new DependencyProperty(){ | ||||
|                       PropertyName=it.property.Name, | ||||
|                       Description=it.devicePropertyAttribute.Description, | ||||
|                       Remark=it.devicePropertyAttribute.Remark, | ||||
|                       Value=it.property.GetValue(driver.DriverPropertys)?.ToString(), | ||||
|                   } | ||||
|               }); | ||||
|         return data.ToList(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取插件的变量上传属性值 | ||||
|     /// </summary> | ||||
|     public List<DependencyProperty> GetDriverVariableProperties(UpLoadBase driver) | ||||
|     { | ||||
|         var data = driver.VariablePropertys?.GetType().GetPropertiesWithCache()?.SelectMany(it => | ||||
|             new[] { new { property = it, devicePropertyAttribute = it.GetCustomAttribute<VariablePropertyAttribute>() } }) | ||||
|             ?.Where(x => x.devicePropertyAttribute != null).ToList() | ||||
|               ?.SelectMany(it => new[] | ||||
|               { | ||||
|                   new DependencyProperty(){ | ||||
|                       PropertyName=it.property.Name, | ||||
|                       Description=it.devicePropertyAttribute.Description, | ||||
|                       Remark=it.devicePropertyAttribute.Remark, | ||||
|                       Value=it.property.GetValue(driver.VariablePropertys)?.ToString(), | ||||
|                   } | ||||
|               }); | ||||
|         return data?.ToList(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取插件方法 | ||||
|     /// </summary> | ||||
|     /// <param name="driver"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<MethodInfo> GetMethod(DriverBase driver) | ||||
|     { | ||||
|         return driver.GetType().GetMethods().Where( | ||||
|                x => x.GetCustomAttribute(typeof(DeviceMethodAttribute)) != null).ToList(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置插件的属性值 | ||||
|     /// </summary> | ||||
|     public void SetDriverProperties(DriverBase driver, List<DependencyProperty> deviceProperties) | ||||
|     { | ||||
|         var pluginPropertys = driver.DriverPropertys?.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute(typeof(DevicePropertyAttribute)) != null)?.ToList(); | ||||
|         foreach (var propertyInfo in pluginPropertys ?? new()) | ||||
|         { | ||||
|             var deviceProperty = deviceProperties.FirstOrDefault(x => x.PropertyName == propertyInfo.Name); | ||||
|             if (deviceProperty == null) continue; | ||||
|             var value = ReadWriteHelpers.ObjToTypeValue(propertyInfo, deviceProperty?.Value ?? ""); | ||||
|             propertyInfo.SetValue(driver.DriverPropertys, value); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 尝试添加插件,返回插件表示类,方法完成后会完全卸载插件 | ||||
|     /// </summary> | ||||
|     /// <param name="plugin"></param> | ||||
|     /// <returns></returns> | ||||
|     public async Task<List<DriverPlugin>> TestAddDriverAsync(DriverPluginAddInput plugin) | ||||
|     { | ||||
|         var assemblyLoadContext = new AssemblyLoadContext(YitIdHelper.NextId().ToString(), true); | ||||
|         try | ||||
|         { | ||||
|  | ||||
|             var driverPlugins = new List<DriverPlugin>(); | ||||
|             var maxFileSize = 100 * 1024 * 1024;//最大100m | ||||
|             //主程序集名称 | ||||
|             var mainFileName = Path.GetFileNameWithoutExtension(plugin.MainFile.Name); | ||||
|             //插件文件夹相对路径 | ||||
|             var dir = "Plugins".CombinePathOS(mainFileName); | ||||
|             //插件文件夹绝对路径 | ||||
|             var fullDir = AppContext.BaseDirectory.CombinePathOS(dir); | ||||
|             //主程序集相对路径 | ||||
|             var path = dir.CombinePathOS(plugin.MainFile.Name); | ||||
|             //主程序集绝对路径 | ||||
|             var fullPath = fullDir.CombinePathOS(plugin.MainFile.Name); | ||||
|  | ||||
|             //主程序集相对路径 | ||||
|             //获取文件流 | ||||
|             using var stream = plugin.MainFile.OpenReadStream(maxFileSize); | ||||
|             Directory.CreateDirectory(fullDir);//创建插件文件夹 | ||||
|             using FileStream fs = new(fullPath, FileMode.Create); | ||||
|             await stream.CopyToAsync(fs); | ||||
|             fs.Seek(0, SeekOrigin.Begin); | ||||
|  | ||||
|             //获取主程序集 | ||||
|             var assembly = assemblyLoadContext.LoadFromStream(fs); | ||||
|             foreach (var item in plugin.OtherFiles) | ||||
|             { | ||||
|                 using var otherStream = item.OpenReadStream(maxFileSize); | ||||
|                 using FileStream fs1 = new(fullDir.CombinePathOS(item.Name), FileMode.Create); | ||||
|                 await otherStream.CopyToAsync(fs1); | ||||
|                 fs1.Seek(0, SeekOrigin.Begin); | ||||
|                 try | ||||
|                 { | ||||
|                     assemblyLoadContext.LoadFromStream(fs1); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger.LogWarning($"尝试加载附属程序集{item}失败,如果此程序集为非引用,比如非托管DllImport,可以忽略此警告。错误信息:{(ex.Message)}"); | ||||
|                 } | ||||
|             } | ||||
|             if (assembly != null) | ||||
|             { | ||||
|                 //获取插件的相关信息 | ||||
|                 var collectBase = assembly.GetTypes().Where(x => (typeof(CollectBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList(); | ||||
|                 for (int i = 0; i < collectBase.Count; i++) | ||||
|                 { | ||||
|                     var item = collectBase[i]; | ||||
|                     driverPlugins.Add(new DriverPlugin() | ||||
|                     { | ||||
|                         AssembleName = item.Name, | ||||
|                         DriverTypeEnum = DriverEnum.Collect, | ||||
|                         FilePath = path, | ||||
|                         FileName = mainFileName, | ||||
|                     }); | ||||
|                 } | ||||
|                 collectBase.ForEach(a => a = null); | ||||
|                 collectBase.Clear(); | ||||
|                 collectBase = null; | ||||
|                 var upLoadBase = assembly.GetTypes().Where(x => (typeof(UpLoadBase).IsAssignableFrom(x)) && x.IsClass && !x.IsAbstract).ToList(); | ||||
|                 for (int i = 0; i < upLoadBase.Count; i++) | ||||
|                 { | ||||
|                     var item = upLoadBase[i]; | ||||
|                     driverPlugins.Add(new DriverPlugin() | ||||
|                     { | ||||
|                         AssembleName = item.Name, | ||||
|                         DriverTypeEnum = DriverEnum.Upload, | ||||
|                         FilePath = path, | ||||
|                         FileName = mainFileName, | ||||
|                     }); | ||||
|                 } | ||||
|                 upLoadBase.ForEach(a => a = null); | ||||
|                 upLoadBase.Clear(); | ||||
|                 upLoadBase = null; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 throw Oops.Bah("加载驱动文件失败"); | ||||
|             } | ||||
|             if (driverPlugins.Count == 0) | ||||
|             { | ||||
|                 throw Oops.Bah("找不到对应的驱动"); | ||||
|             } | ||||
|             assembly = null; | ||||
|             return driverPlugins; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             assemblyLoadContext.Unload(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,158 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
|  | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
|  | ||||
| /// <summary> | ||||
| /// 上传插件 | ||||
| /// <para></para> | ||||
| /// 约定: | ||||
| /// <para></para> | ||||
| /// 如果设备属性需要密码输入,属性名称中需包含Password字符串 | ||||
| /// <para></para> | ||||
| /// 如果设备属性需要大文本输入,属性名称中需包含BigText字符串 | ||||
| /// <br></br> | ||||
| /// 因为自定义上传插件需求比较大,这里着重解释代码运行原理 | ||||
| /// 继承<see cref="UpLoadBase"/>后,可以看到需要实现各类虚方法/属性<br></br> | ||||
| /// <see cref="UploadVariables"/> <br></br> | ||||
| /// <see cref="VariablePropertys"/><br></br> | ||||
| /// <see cref="DriverBase.DriverPropertys"/><br></br> | ||||
| /// <see cref="BeforStartAsync"/><br></br> | ||||
| /// <see cref="ExecuteAsync"/><br></br> | ||||
| /// <see cref="DriverBase.IsConnected"/><br></br> | ||||
| /// <see cref="Init(UploadDeviceRunTime)"/><br></br> | ||||
| /// 含义可看注释,下面看看网关上传插件的生命周期<br></br> | ||||
| /// 1、构造函数<see cref="UpLoadBase()"/> 传入参数服务工厂,在需要获取服务时使用<see cref="App.GetService{TService}(IServiceProvider)"/><br></br> | ||||
| /// 2、<see cref="Init(UploadDeviceRunTime)"/>初始化函数,传入上传设备参数,只执行一次,在这个方法内,一般会初始化一些必要的实例,比如new MqttClient,以及一些必要的实现属性,比如<see cref="UploadVariables"/><br></br> | ||||
| /// 3、<see cref="BeforStartAsync"/>开始前执行的方法,比如连接mqtt等,只执行一次<br></br> | ||||
| /// 4、<see cref="ExecuteAsync"/>核心执行的方法,需实现上传方法,在插件结束前会一直循环调用<br></br> | ||||
| /// 5、<see cref="DisposableObject.Dispose(bool)"/> 结束时调用的方法,实现资源释放方法<br></br> | ||||
| /// 网关的数据是如何传入到上传插件的,下面会以Mqtt上传为例<br></br> | ||||
| /// 1、如何获取采集变量值?在初始化函数中<see cref="Init(UploadDeviceRunTime)"/>获取全局设备/变量<br></br> | ||||
| /// 通过<see cref="App.GetService{TService}(IServiceProvider)"/>获取单例服务<see cref="GlobalDeviceData"/><br></br> | ||||
| /// 可以看到在这个单例服务中,已经拥有全部的采集设备与变量<br></br> | ||||
| /// 2、如何获取采集变量中的上传属性?UploadBase中封装了通用方法<see cref="GetPropertyValue(DeviceVariableRunTime, string)"/><br></br> | ||||
| /// 比如定义了变量属性Enable,只有设置为true的变量才会用作某逻辑,执行方法GetPropertyValue(tag,"Enable"),也可用硬编码传入propertyName参数<br></br> | ||||
| /// 3、如何定义自己的上传实体,第一步中获取获取单例服务<see cref="GlobalDeviceData"/>,在拥有全局变量下,可以使用<see cref="Mapster"/> 或者 手动赋值到DTO实体<br></br> | ||||
| /// 4、完整的参考可以查看MqttClient插件ThingsGateway\src\Plugins\ThingsGateway.Mqtt\ThingsGateway.Mqtt.csproj<br></br> | ||||
| /// </summary> | ||||
| public abstract class UpLoadBase : DriverBase | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 当前上传设备 | ||||
|     /// </summary> | ||||
|     public UploadDeviceRunTime CurDevice { get; protected set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 返回插件的上传变量,一般在<see cref="Init(UploadDeviceRunTime)"/>后初始化 | ||||
|     /// </summary> | ||||
|     public abstract List<DeviceVariableRunTime> UploadVariables { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件配置项 ,继承实现<see cref="VariablePropertyBase"/>后,返回继承类,如果不存在,返回null | ||||
|     /// </summary> | ||||
|     public abstract VariablePropertyBase VariablePropertys { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 离线缓存 | ||||
|     /// </summary> | ||||
|     protected CacheDb CacheDb { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 结束通讯后执行的方法 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract Task AfterStopAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始执行的方法 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public abstract Task BeforStartAsync(CancellationToken token); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 循环执行 | ||||
|     /// </summary> | ||||
|     public abstract Task ExecuteAsync(CancellationToken token); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取设备的属性值 | ||||
|     /// </summary> | ||||
|     public virtual string GetDevicePropertyValue(CollectDeviceRunTime collectDeviceRunTime, string propertyName) | ||||
|     { | ||||
|  | ||||
|         if (collectDeviceRunTime == null) | ||||
|             return null; | ||||
|         return collectDeviceRunTime.DevicePropertys.FirstOrDefault(a => a.PropertyName == propertyName).Value; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取变量的属性值 | ||||
|     /// </summary> | ||||
|     public virtual string GetPropertyValue(DeviceVariableRunTime variableRunTime, string propertyName) | ||||
|     { | ||||
|         if (variableRunTime == null) | ||||
|             return null; | ||||
|         if (variableRunTime.VariablePropertys.ContainsKey(CurDevice.Id)) | ||||
|         { | ||||
|             var data = variableRunTime.VariablePropertys[CurDevice.Id].FirstOrDefault(a => | ||||
|                   a.PropertyName == propertyName); | ||||
|             if (data != null) | ||||
|             { | ||||
|                 return data.Value; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     public void Init(ILogger logger, UploadDeviceRunTime device) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         IsLogOut = device.IsLogOut; | ||||
|         CurDevice = device; | ||||
|         CacheDb = new(CurDevice.Id.ToString()); | ||||
|         Init(device); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     /// <param name="device">设备</param> | ||||
|     protected abstract void Init(UploadDeviceRunTime device); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 底层日志输出 | ||||
|     /// </summary> | ||||
|     protected override void Log_Out(TouchSocket.Core.LogLevel arg1, object arg2, string arg3, Exception arg4) | ||||
|     { | ||||
|         if (arg1 >= TouchSocket.Core.LogLevel.Warning) | ||||
|         { | ||||
|             CurDevice.SetDeviceStatus(lastErrorMessage: arg3); | ||||
|         } | ||||
|         if (IsLogOut || arg1 >= TouchSocket.Core.LogLevel.Warning) | ||||
|         { | ||||
|             _logger.Log_Out(arg1, arg2, arg3, arg4); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,156 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.Foundation.Extension.ConcurrentQueue; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 变量写入/执行变量附带方法,单例服务 | ||||
| /// </summary> | ||||
| public class RpcSingletonService : ISingleton | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 写入变量说明 | ||||
|     /// </summary> | ||||
|     public const string WriteVariable = "写入变量"; | ||||
|     private readonly ILogger<RpcSingletonService> _logger; | ||||
|     private readonly CollectDeviceWorker _collectDeviceHostService; | ||||
|     private readonly GlobalDeviceData _globalDeviceData; | ||||
|     private readonly ConcurrentQueue<RpcLog> _logQueues = new(); | ||||
|     /// <inheritdoc cref="RpcSingletonService"/> | ||||
|     public RpcSingletonService(ILogger<RpcSingletonService> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         _globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>(); | ||||
|         _collectDeviceHostService = ServiceHelper.GetBackgroundService<CollectDeviceWorker>(); | ||||
|         Task.Factory.StartNew(RpcLogInsertAsync, TaskCreationOptions.LongRunning); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 反向RPC入口方法 | ||||
|     /// </summary> | ||||
|     /// <param name="sourceDes">触发该方法的源说明</param> | ||||
|     /// <param name="item">指定键为变量名称,值为附带方法参数或写入值</param> | ||||
|     /// <param name="isBlazor">如果是true,不检查<see cref="MemoryVariable.RpcWriteEnable"/>字段</param> | ||||
|     /// <param name="token"><see cref="CancellationToken"/> 取消源</param> | ||||
|     /// <returns></returns> | ||||
|     public async Task<OperResult> InvokeDeviceMethodAsync(string sourceDes, KeyValuePair<string, string> item, bool isBlazor = false, CancellationToken token = default) | ||||
|     { | ||||
|         //避免并发过高,这里延时10ms | ||||
|         await Task.Delay(10, token); | ||||
|         OperResult data = new(); | ||||
|         var tag = _globalDeviceData.AllVariables.FirstOrDefault(it => it.Name == item.Key); | ||||
|         if (tag == null) return new OperResult("不存在变量:" + item.Key); | ||||
|         if (tag.ProtectTypeEnum == ProtectTypeEnum.ReadOnly) return new OperResult("只读变量"); | ||||
|         if (!tag.RpcWriteEnable && !isBlazor) return new OperResult("不允许远程写入"); | ||||
|  | ||||
|         if (tag.IsMemoryVariable == true) | ||||
|         { | ||||
|             return tag.SetValue(item.Value); | ||||
|         } | ||||
|         var dev = _collectDeviceHostService.CollectDeviceCores.FirstOrDefault(it => it.Device.Id == tag.DeviceId); | ||||
|         if (dev == null) return new OperResult("系统错误,不存在对应采集设备,请稍候重试"); | ||||
|         if (dev.Device.DeviceStatus == DeviceStatusEnum.OffLine) return new OperResult("设备已离线"); | ||||
|         if (dev.Device.DeviceStatus == DeviceStatusEnum.Pause) return new OperResult("设备已暂停"); | ||||
|         if (string.IsNullOrEmpty(tag.OtherMethod)) | ||||
|         { | ||||
|             //写入变量 | ||||
|             JToken tagValue; | ||||
|             try | ||||
|             { | ||||
|                 tagValue = JToken.Parse(item.Value); | ||||
|             } | ||||
|             catch (Exception) | ||||
|             { | ||||
|                 tagValue = JToken.Parse("\"" + item.Value + "\""); | ||||
|             } | ||||
|  | ||||
|             data = await dev.InVokeWriteAsync(tag, tagValue, token); | ||||
|             _logQueues.Enqueue( | ||||
|                 new RpcLog() | ||||
|                 { | ||||
|                     LogTime = SysDateTimeExtensions.CurrentDateTime, | ||||
|                     OperateMessage = data.Exception, | ||||
|                     IsSuccess = data.IsSuccess, | ||||
|                     OperateMethod = WriteVariable, | ||||
|                     OperateObject = tag.Name, | ||||
|                     OperateSource = sourceDes, | ||||
|                     ParamJson = item.Value, | ||||
|                     ResultJson = data.Message | ||||
|                 } | ||||
|                 ); | ||||
|             if (!data.IsSuccess) | ||||
|             { | ||||
|                 _logger.LogWarning($"写入变量[{tag.Name}]失败:{data.Message}"); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             //执行变量附带的方法 | ||||
|             var method = dev.DeviceVariableMethodSources.FirstOrDefault(it => it.DeviceVariable == tag); | ||||
|             try | ||||
|             { | ||||
|                 data = await dev.InvokeMethodAsync(method, false, item.Value, token); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 data = new OperResult<string>(ex); | ||||
|             } | ||||
|             _logQueues.Enqueue( | ||||
| new RpcLog() | ||||
| { | ||||
|     LogTime = SysDateTimeExtensions.CurrentDateTime, | ||||
|     OperateMessage = data.Exception, | ||||
|     IsSuccess = data.IsSuccess, | ||||
|     OperateMethod = tag.OtherMethod, | ||||
|     OperateObject = tag.Name, | ||||
|     OperateSource = sourceDes, | ||||
|     ParamJson = item.Value?.ToString(), | ||||
|     ResultJson = data.Message | ||||
| } | ||||
| ); | ||||
|             if (!data.IsSuccess) | ||||
|             { | ||||
|                 _logger.LogWarning($"执行变量[{tag.Name}]方法[{tag.OtherMethod}]失败:{data.Message}"); | ||||
|             } | ||||
|         } | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     private async Task RpcLogInsertAsync() | ||||
|     { | ||||
|         var db = DbContext.Db.CopyNew(); | ||||
|         while (true) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var data = _logQueues.ToListWithDequeue(); | ||||
|                 db.InsertableWithAttr(data).ExecuteCommand();//入库 | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|  | ||||
|             } | ||||
|  | ||||
|             await Task.Delay(3000); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 系统配置种子数据 | ||||
| /// </summary> | ||||
| public class DriverPluginSeedData : ISqlSugarEntitySeedData<DriverPlugin> | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public IEnumerable<DriverPlugin> SeedData() | ||||
|     { | ||||
|         return SeedDataUtil.GetSeedData<DriverPlugin>("driver_plugin.json"); | ||||
|     } | ||||
| } | ||||
| @@ -1,324 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": 319003388334342, | ||||
|       "FileName": "ThingsGateway.Modbus", | ||||
|       "AssembleName": "ModbusRtu", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll", | ||||
|       "CreateTime": "2023/2/23 15:18:52", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": "2023/3/2 12:40:28", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": 212725263002001, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 389003388313861, | ||||
|       "FileName": "ThingsGateway.Modbus", | ||||
|       "AssembleName": "ModbusRtuOverTcp", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll", | ||||
|       "CreateTime": "2023/2/23 15:18:52", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": "2023/3/2 12:40:28", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": 212725263002001, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 389003388334341, | ||||
|       "FileName": "ThingsGateway.Modbus", | ||||
|       "AssembleName": "ModbusRtuOverUdp", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll", | ||||
|       "CreateTime": "2023/2/23 15:18:52", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": "2023/3/2 12:40:28", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": 212725263002001, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 389003388334342, | ||||
|       "FileName": "ThingsGateway.Modbus", | ||||
|       "AssembleName": "ModbusTcp", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll", | ||||
|       "CreateTime": "2023/2/23 15:18:52", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": "2023/3/2 12:40:28", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": 212725263002001, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 389003388334343, | ||||
|       "FileName": "ThingsGateway.Modbus", | ||||
|       "AssembleName": "ModbusUdp", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll", | ||||
|       "CreateTime": "2023/2/23 15:18:52", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": "2023/3/2 12:40:28", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": 212725263002001, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 391441718112517, | ||||
|       "FileName": "ThingsGateway.Modbus", | ||||
|       "AssembleName": "ModbusServer", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.Modbus/ThingsGateway.Modbus.dll", | ||||
|       "CreateTime": "2023/3/2 12:40:28", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087020826885, | ||||
|       "FileName": "ThingsGateway.OPCUA", | ||||
|       "AssembleName": "OPCUAClient", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.OPCUA/ThingsGateway.OPCUA.dll", | ||||
|       "CreateTime": "2023/8/6 18:21:47", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087020826886, | ||||
|       "FileName": "ThingsGateway.OPCUA", | ||||
|       "AssembleName": "OPCUAServer", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.OPCUA/ThingsGateway.OPCUA.dll", | ||||
|       "CreateTime": "2023/8/6 18:21:47", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087111926021, | ||||
|       "FileName": "ThingsGateway.OPCDA", | ||||
|       "AssembleName": "OPCDAClient", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.OPCDA/ThingsGateway.OPCDA.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:09", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087153508613, | ||||
|       "FileName": "ThingsGateway.RabbitMQ", | ||||
|       "AssembleName": "RabbitMQClient", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.RabbitMQ/ThingsGateway.RabbitMQ.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:19", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087210762501, | ||||
|       "FileName": "ThingsGateway.Siemens", | ||||
|       "AssembleName": "S7_1200", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:33", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087210762502, | ||||
|       "FileName": "ThingsGateway.Siemens", | ||||
|       "AssembleName": "S7_1500", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:33", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087210762503, | ||||
|       "FileName": "ThingsGateway.Siemens", | ||||
|       "AssembleName": "S7_200", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:33", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087210762504, | ||||
|       "FileName": "ThingsGateway.Siemens", | ||||
|       "AssembleName": "S7_200SMART", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:33", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087210762505, | ||||
|       "FileName": "ThingsGateway.Siemens", | ||||
|       "AssembleName": "S7_300", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:33", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087210762506, | ||||
|       "FileName": "ThingsGateway.Siemens", | ||||
|       "AssembleName": "S7_400", | ||||
|       "DriverTypeEnum": "Collect", | ||||
|       "FilePath": "Plugins/ThingsGateway.Siemens/ThingsGateway.Siemens.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:33", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087273050373, | ||||
|       "FileName": "ThingsGateway.Mqtt", | ||||
|       "AssembleName": "IotSharpClient", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.Mqtt/ThingsGateway.Mqtt.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:48", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087273050374, | ||||
|       "FileName": "ThingsGateway.Mqtt", | ||||
|       "AssembleName": "MqttClient", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.Mqtt/ThingsGateway.Mqtt.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:48", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087273050375, | ||||
|       "FileName": "ThingsGateway.Mqtt", | ||||
|       "AssembleName": "MqttServer", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.Mqtt/ThingsGateway.Mqtt.dll", | ||||
|       "CreateTime": "2023/8/6 18:22:48", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     }, | ||||
|     { | ||||
|       "Id": 447087330930949, | ||||
|       "FileName": "ThingsGateway.Kafka", | ||||
|       "AssembleName": "KafkaProducer", | ||||
|       "DriverTypeEnum": "Upload", | ||||
|       "FilePath": "Plugins/ThingsGateway.Kafka/ThingsGateway.Kafka.dll", | ||||
|       "CreateTime": "2023/8/6 18:23:02", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": 212725263002001, | ||||
|       "IsDelete": 0, | ||||
|       "UpdateTime": null, | ||||
|       "UpdateUser": null, | ||||
|       "UpdateUserId": null, | ||||
|       "SortCode": 0, | ||||
|       "ExtJson": null | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,76 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": "230000000000", | ||||
|       "Category": "THINGSGATEWAY_ALARMCONFIG_BASE", | ||||
|       "ConfigKey": "CONFIG_ALARM_ENABLE", | ||||
|       "ConfigValue": "False", | ||||
|       "Remark": "转储使能", | ||||
|       "SortCode": "1", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:50:38.341", | ||||
|       "UpdateUser": "admin", | ||||
|       "UpdateUserId": "201725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "230000000001", | ||||
|       "Category": "THINGSGATEWAY_ALARMCONFIG_BASE", | ||||
|       "ConfigKey": "CONFIG_ALARM_DBTYPE", | ||||
|       "ConfigValue": "SqlServer", | ||||
|       "Remark": "数据库类型", | ||||
|       "SortCode": "2", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:50:38.343", | ||||
|       "UpdateUser": "admin", | ||||
|       "UpdateUserId": "201725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "230000000002", | ||||
|       "Category": "THINGSGATEWAY_ALARMCONFIG_BASE", | ||||
|       "ConfigKey": "CONFIG_ALARM_CONNSTR", | ||||
|       "ConfigValue": "server=.;uid=sa;pwd=111111;database=test", | ||||
|       "Remark": "连接字符串", | ||||
|       "SortCode": "2", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:50:38.343", | ||||
|       "UpdateUser": "admin", | ||||
|       "UpdateUserId": "201725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "240000000000", | ||||
|       "Category": "THINGSGATEWAY_HISCONFIG_BASE", | ||||
|       "ConfigKey": "CONFIG_HIS_ENABLE", | ||||
|       "ConfigValue": "False", | ||||
|       "Remark": "转储使能", | ||||
|       "SortCode": "1", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-02-26 17:38:37.741", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "240000000001", | ||||
|       "Category": "THINGSGATEWAY_HISCONFIG_BASE", | ||||
|       "ConfigKey": "CONFIG_HIS_DBTYPE", | ||||
|       "ConfigValue": "QuestDB", | ||||
|       "Remark": "数据库类型", | ||||
|       "SortCode": "2", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-02-26 17:38:37.744", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "240000000002", | ||||
|       "Category": "THINGSGATEWAY_HISCONFIG_BASE", | ||||
|       "ConfigKey": "CONFIG_HIS_CONNSTR", | ||||
|       "ConfigValue": "host=localhost;port=8812;username=admin;password=quest;database=qdb;ServerCompatibilityMode=NoTypeLoading;", | ||||
|       "Remark": "连接字符串", | ||||
|       "SortCode": "2", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-02-26 17:38:37.745", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,492 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": "200001", | ||||
|       "Title": "网关配置", | ||||
|       "Icon": "mdi-cog", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "0", | ||||
|       "SortCode": "2", | ||||
|       "TargetType": "None", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:43:27.6423987", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001001", | ||||
|       "Title": "采集设备", | ||||
|       "Icon": "mdi-database-cog-outline", | ||||
|       "Name": "gatewayCollectDevice", | ||||
|       "Component": "/gatewayconfig/collectdevice", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "2", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:58:03.8016224", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001002", | ||||
|       "Title": "插件管理", | ||||
|       "Icon": "mdi-database-cog-outline", | ||||
|       "Name": "gatewayplugin", | ||||
|       "Component": "/gatewayconfig/plugin", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "1", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:58:00.8434735", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001011", | ||||
|       "Title": "驱动调试", | ||||
|       "Icon": "mdi-database-cog-outline", | ||||
|       "Name": "gatewaydriverdebug", | ||||
|       "Component": "/gatewayconfig/driverdebug", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "1", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:58:09.8844904", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001003", | ||||
|       "Title": "变量管理", | ||||
|       "Icon": "mdi-database-cog-outline", | ||||
|       "Name": "gatewayvariable", | ||||
|       "Component": "/gatewayconfig/devicevariable", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "3", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:58:09.8844904", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001903", | ||||
|       "Title": "中间变量", | ||||
|       "Icon": "mdi-database-sync-outline", | ||||
|       "Component": "/gatewayconfig/memoryvariable", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "3", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 01:02:12.089", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:01:49.2309339", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001004", | ||||
|       "Title": "运行状态", | ||||
|       "Icon": "mdi-transit-connection-horizontal", | ||||
|       "Component": "/gatewayruntime/devicestatus", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "389850957095173", | ||||
|       "SortCode": "1", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 21:23:45.8478018", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001005", | ||||
|       "Title": "实时数据", | ||||
|       "Icon": "mdi-database-refresh-outline", | ||||
|       "Component": "/gatewayruntime/devicevariable", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "389850957095173", | ||||
|       "SortCode": "2", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:59:12.0176321", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "389850957095173", | ||||
|       "Title": "网关状态", | ||||
|       "Icon": "mdi-transit-connection-variant", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "0", | ||||
|       "SortCode": "3", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-02-26 00:47:38.342", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:55:58.2367985", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "389854423286021", | ||||
|       "Title": "硬件信息", | ||||
|       "Icon": "mdi-memory", | ||||
|       "Component": "/gatewayruntime/hardwareinfo", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "389850957095173", | ||||
|       "SortCode": "4", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 01:01:44.580", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 21:24:02.3314076", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "389854535966981", | ||||
|       "Title": "历史数据", | ||||
|       "Icon": "mdi-database-sync-outline", | ||||
|       "Component": "/gatewayruntime/historyvalue", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "389850957095173", | ||||
|       "SortCode": "5", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 01:02:12.089", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:01:49.2309339", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|  | ||||
|  | ||||
|     { | ||||
|       "Id": "389854579716357", | ||||
|       "Title": "实时报警", | ||||
|       "Icon": "mdi-alarm-light-outline", | ||||
|       "Component": "/gatewayruntime/realalarm", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "389850957095173", | ||||
|       "SortCode": "3", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 01:02:22.771", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:59:32.6305265", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "389854617587973", | ||||
|       "Title": "历史报警", | ||||
|       "Icon": "mdi-database-sync-outline", | ||||
|       "Component": "/gatewayruntime/hisalarm", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "389850957095173", | ||||
|       "SortCode": "6", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 01:02:32.016", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:01:52.9110511", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "390053150736645", | ||||
|       "Title": "其他配置", | ||||
|       "Icon": "mdi-database-cog-outline", | ||||
|       "Component": "/gatewayconfig/config", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "4", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 14:30:22.022", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:50:26.0961596", | ||||
|       "UpdateUser": "admin", | ||||
|       "UpdateUserId": "201725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "390107241025797", | ||||
|       "Title": "网关日志", | ||||
|       "Icon": "mdi-database-search-outline", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "0", | ||||
|       "SortCode": "3", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-02-26 18:10:27.657", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:56:30.9492488", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "390107473895685", | ||||
|       "Title": "Rpc日志", | ||||
|       "Icon": "mdi-database-search-outline", | ||||
|       "Component": "/gatewaylog/rpclog", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "390107241025797", | ||||
|       "SortCode": "1", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 18:11:24.513", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:02:10.321289", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "390107521245445", | ||||
|       "Title": "后台日志", | ||||
|       "Icon": "mdi-database-search-outline", | ||||
|       "Component": "/gatewaylog/backendlog", | ||||
|       "Category": "MENU", | ||||
|       "ParentId": "390107241025797", | ||||
|       "SortCode": "1", | ||||
|       "TargetType": "SELF", | ||||
|       "CreateTime": "2023-02-26 18:11:36.074", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 18:02:13.1842573", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "200001101", | ||||
|       "Title": "上传设备", | ||||
|       "Icon": "mdi-database-cog-outline", | ||||
|       "Name": "gatewayUploadDevice", | ||||
|       "Component": "/gatewayconfig/uploaddevice", | ||||
|       "Category": "MENU", | ||||
|       "Code": "system", | ||||
|       "ParentId": "200001", | ||||
|       "SortCode": "2", | ||||
|       "TargetType": "SELF", | ||||
|       "IsDelete": false, | ||||
|       "UpdateTime": "2023-03-03 17:58:06.9967903", | ||||
|       "UpdateUser": "superAdmin", | ||||
|       "UpdateUserId": "212725263002001" | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391876432142597", | ||||
|       "Title": "清空", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayrpclogclear", | ||||
|       "ParentId": "390107473895685", | ||||
|       "SortCode": "1", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:09:19.0789355", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391876531253509", | ||||
|       "Title": "清空", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaybackendlogclear", | ||||
|       "ParentId": "390107521245445", | ||||
|       "SortCode": "2", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:09:43.2483009", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391876932448517", | ||||
|       "Title": "采集设备暂停", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaydevicepause", | ||||
|       "ParentId": "200001004", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:11:21.2313178", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391876995248389", | ||||
|       "Title": "设备重启", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaydevicerestart", | ||||
|       "ParentId": "200001004", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:11:36.5638931", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877277679877", | ||||
|       "Title": "添加", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaypluginadd", | ||||
|       "ParentId": "200001002", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:12:45.5166707", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877356581125", | ||||
|       "Title": "添加", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaycollectdeviceadd", | ||||
|       "ParentId": "200001001", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:13:04.7786123", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877402190085", | ||||
|       "Title": "修改", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaycollectdeviceedit", | ||||
|       "ParentId": "200001001", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:13:15.9140326", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877432721669", | ||||
|       "Title": "删除", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewaycollectdevicedelete", | ||||
|       "ParentId": "200001001", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:13:23.3685636", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877521359109", | ||||
|       "Title": "添加", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayvariableadd", | ||||
|       "ParentId": "200001003", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:13:45.0088195", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877554565381", | ||||
|       "Title": "修改", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayvariableedit", | ||||
|       "ParentId": "200001003", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:13:53.1151901", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877580439813", | ||||
|       "Title": "删除", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayvariabledelete", | ||||
|       "ParentId": "200001003", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:13:59.4322888", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877764051205", | ||||
|       "Title": "添加", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayuploaddeviceadd", | ||||
|       "ParentId": "200001101", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:14:44.259772", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877800583429", | ||||
|       "Title": "修改", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayuploaddeviceedit", | ||||
|       "ParentId": "200001101", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:14:53.178319", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391877824180485", | ||||
|       "Title": "删除", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayuploaddevicedelete", | ||||
|       "ParentId": "200001101", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:14:58.9389864", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391878193139973", | ||||
|       "Title": "报警配置保存", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayalarmconfig", | ||||
|       "ParentId": "390053150736645", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:16:29.0170337", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     }, | ||||
|     { | ||||
|       "Id": "391878242271493", | ||||
|       "Title": "历史配置保存", | ||||
|       "Category": "BUTTON", | ||||
|       "Code": "gatewayhisconfig", | ||||
|       "ParentId": "390053150736645", | ||||
|       "TargetType": "None", | ||||
|       "CreateTime": "2023-03-03 18:16:41.0119884", | ||||
|       "CreateUser": "superAdmin", | ||||
|       "CreateUserId": "212725263002001", | ||||
|       "IsDelete": false | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,13 +0,0 @@ | ||||
| { | ||||
|   "RECORDS": [ | ||||
|     { | ||||
|       "Id": "257755263002001", | ||||
|       "Account": "admin", | ||||
|       "Password": "7DA385A25A98388E", | ||||
|       "UserEnable": "true", | ||||
|       "SortCode": "1", | ||||
|       "IsDelete": "false", | ||||
|       "PermissionCodeList": [ "/openApi/collectInfo/collectDeviceList", "/openApi/collectInfo/collectVariableList", "/openApi/collectInfo/realAlarmList", "/openApi/rpc/writeDeviceMethod", "/openApi/rpc/writeVariables", "/openApi/rpc/configDeviceThread", "/openApi/rpc/upDeviceThread" ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 资源表种子数据 | ||||
| /// </summary> | ||||
| public class SysResourceSeedData : ISqlSugarEntitySeedData<SysResource> | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public IEnumerable<SysResource> SeedData() | ||||
|     { | ||||
|         return SeedDataUtil.GetSeedData<SysResource>("gateway_resource.json"); | ||||
|     } | ||||
| } | ||||
| @@ -1,523 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
| using Furion.DependencyInjection; | ||||
| using Furion.FriendlyException; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| using MiniExcelLibs; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Dynamic; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
|  | ||||
| using Yitter.IdGenerator; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <inheritdoc cref="ICollectDeviceService"/> | ||||
| [Injection(Proxy = typeof(OperDispatchProxy))] | ||||
| public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceService | ||||
| { | ||||
|     private readonly IDriverPluginService _driverPluginService; | ||||
|     private readonly IFileService _fileService; | ||||
|  | ||||
|     /// <inheritdoc cref="ICollectDeviceService"/> | ||||
|     public CollectDeviceService( | ||||
|     IDriverPluginService driverPluginService, | ||||
|     IFileService fileService | ||||
|         ) | ||||
|     { | ||||
|         _fileService = fileService; | ||||
|  | ||||
|         _driverPluginService = driverPluginService; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("添加采集设备")] | ||||
|     public async Task AddAsync(CollectDevice input) | ||||
|     { | ||||
|         var account_Id = GetIdByName(input.Name); | ||||
|         if (account_Id > 0) | ||||
|             throw Oops.Bah($"存在重复的名称:{input.Name}"); | ||||
|         await InsertAsync(input);//添加数据 | ||||
|         CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除 | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("复制采集设备")] | ||||
|     public async Task CopyDevAsync(IEnumerable<CollectDevice> input) | ||||
|     { | ||||
|         var newDevs = input.Adapt<List<CollectDevice>>(); | ||||
|         newDevs.ForEach(a => | ||||
|         { | ||||
|             a.Id = YitIdHelper.NextId(); | ||||
|             a.Name = $"Copy-{a.Name}-{a.Id}"; | ||||
|         }); | ||||
|  | ||||
|         var result = await InsertRangeAsync(newDevs);//添加数据 | ||||
|         CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除 | ||||
|  | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("复制采集设备与变量")] | ||||
|     public async Task CopyDevAndVarAsync(IEnumerable<CollectDevice> input) | ||||
|     { | ||||
|         var variableService = App.GetService<IVariableService>(); | ||||
|         List<DeviceVariable> variables = new(); | ||||
|         var newDevs = input.Adapt<List<CollectDevice>>(); | ||||
|         foreach (var item in newDevs) | ||||
|         { | ||||
|             var newId = YitIdHelper.NextId(); | ||||
|             var deviceVariables = await Context.Queryable<DeviceVariable>().Where(a => a.DeviceId == item.Id).ToListAsync(); | ||||
|             deviceVariables.ForEach(b => | ||||
|             { | ||||
|                 b.Id = YitIdHelper.NextId(); | ||||
|                 b.DeviceId = newId; | ||||
|                 b.Name = $"Copy-{b.Name}-{b.Id}"; | ||||
|             }); | ||||
|             variables.AddRange(deviceVariables); | ||||
|             item.Id = newId; | ||||
|             item.Name = $"Copy-{item.Name}-{newId}"; | ||||
|         } | ||||
|  | ||||
|         var result = await itenant.UseTranAsync(async () => | ||||
|         { | ||||
|             await InsertRangeAsync(newDevs);//添加数据 | ||||
|             await Context.Insertable(variables).ExecuteCommandAsync();//添加数据 | ||||
|         }); | ||||
|  | ||||
|         if (result.IsSuccess) | ||||
|         { | ||||
|             CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除 | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw Oops.Oh(result.ErrorMessage); | ||||
|         } | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public long? GetIdByName(string name) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         return data.FirstOrDefault(it => it.Name == name)?.Id; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public string GetNameById(long id) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         return data.FirstOrDefault(it => it.Id == id)?.Name; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public List<DeviceTree> GetTree() | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         var trees = data.GetTree(); | ||||
|         return trees; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("删除采集设备")] | ||||
|     public async Task DeleteAsync(params long[] input) | ||||
|     { | ||||
|         //获取所有ID | ||||
|         if (input.Length > 0) | ||||
|         { | ||||
|             var result = await DeleteByIdsAsync(input.Cast<object>().ToArray()); | ||||
|             var variableService = App.GetService<IVariableService>(); | ||||
|             await Context.Deleteable<DeviceVariable>(it => input.Contains(it.DeviceId)).ExecuteCommandAsync(); | ||||
|             variableService.DeleteVariableFromCache(); | ||||
|             if (result) | ||||
|             { | ||||
|                 CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("编辑采集设备")] | ||||
|     public async Task EditAsync(CollectDeviceEditInput input) | ||||
|     { | ||||
|         var account_Id = GetIdByName(input.Name); | ||||
|         if (account_Id > 0 && account_Id != input.Id) | ||||
|             throw Oops.Bah($"存在重复的名称:{input.Name}"); | ||||
|  | ||||
|         if (await Context.Updateable(input.Adapt<CollectDevice>()).ExecuteCommandAsync() > 0)//修改数据 | ||||
|             CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除 | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<SqlSugarPagedList<CollectDevice>> PageAsync(CollectDevicePageInput input) | ||||
|     { | ||||
|         var query = GetPage(input); | ||||
|         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||
|         return pageInfo; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     private ISugarQueryable<CollectDevice> GetPage(CollectDevicePageInput input) | ||||
|     { | ||||
|         long? pluginid = 0; | ||||
|         if (!string.IsNullOrEmpty(input.PluginName)) | ||||
|         { | ||||
|             pluginid = _driverPluginService.GetCacheList(false).FirstOrDefault(it => it.AssembleName.Contains(input.PluginName))?.Id; | ||||
|         } | ||||
|         ISugarQueryable<CollectDevice> query = Context.Queryable<CollectDevice>() | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.Name), u => u.Name.Contains(input.Name)) | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.DeviceGroup), u => u.DeviceGroup == input.DeviceGroup) | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.PluginName), u => u.PluginId == (pluginid ?? 0)); | ||||
|         for (int i = 0; i < input.SortField.Count; i++) | ||||
|         { | ||||
|             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||
|         } | ||||
|         query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序 | ||||
|  | ||||
|         return query; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public CollectDevice GetDeviceById(long Id) | ||||
|     { | ||||
|         var data = GetCacheList(); | ||||
|         return data.FirstOrDefault(it => it.Id == Id); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public List<CollectDevice> GetCacheList(bool isMapster = true) | ||||
|     { | ||||
|         //先从Cache拿 | ||||
|         var collectDevice = CacheStatic.Cache.Get<List<CollectDevice>>(ThingsGatewayCacheConst.Cache_CollectDevice, isMapster); | ||||
|         if (collectDevice == null) | ||||
|         { | ||||
|             collectDevice = Context.Queryable<CollectDevice>().ToList(); | ||||
|             if (collectDevice != null) | ||||
|             { | ||||
|                 //插入Cache | ||||
|                 CacheStatic.Cache.Set(ThingsGatewayCacheConst.Cache_CollectDevice, collectDevice, isMapster); | ||||
|             } | ||||
|         } | ||||
|         return collectDevice; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<List<CollectDeviceRunTime>> GetCollectDeviceRuntimeAsync(long devId = 0) | ||||
|     { | ||||
|         if (devId == 0) | ||||
|         { | ||||
|             var devices = GetCacheList(false).Where(a => a.Enable).ToList(); | ||||
|             var runtime = devices.Adapt<List<CollectDeviceRunTime>>().ToDictionary(a => a.Id); | ||||
|             var variableService = App.GetService<IVariableService>(); | ||||
|             var collectVariableRunTimes = await variableService.GetDeviceVariableRuntimeAsync(); | ||||
|             ConcurrentDictionary<long, DriverPlugin> driverPlugins = new(_driverPluginService.GetCacheList(false).ToDictionary(a => a.Id)); | ||||
|             runtime.Values.ParallelForEach(device => | ||||
|            { | ||||
|                driverPlugins.TryGetValue(device.PluginId, out var driverPlugin); | ||||
|                device.PluginName = driverPlugin?.AssembleName; | ||||
|                device.DeviceVariableRunTimes = collectVariableRunTimes.Where(a => a.DeviceId == device.Id).ToList(); | ||||
|            }); | ||||
|  | ||||
|             collectVariableRunTimes.ParallelForEach(variable => | ||||
|             { | ||||
|                 if (runtime.TryGetValue(variable.DeviceId, out var device)) | ||||
|                 { | ||||
|                     variable.CollectDeviceRunTime = device; | ||||
|                     variable.DeviceName = device.Name; | ||||
|                 } | ||||
|             }); | ||||
|             return runtime.Values.ToList(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var device = GetCacheList(false).Where(a => a.Enable).ToList().FirstOrDefault(it => it.Id == devId); | ||||
|             var runtime = device.Adapt<CollectDeviceRunTime>(); | ||||
|             var variableService = App.GetService<IVariableService>(); | ||||
|             if (runtime == null) return new() { runtime }; | ||||
|             var pluginName = _driverPluginService.GetNameById(device.PluginId); | ||||
|             var collectVariableRunTimes = await variableService.GetDeviceVariableRuntimeAsync(devId); | ||||
|             runtime.PluginName = pluginName; | ||||
|             runtime.DeviceVariableRunTimes = collectVariableRunTimes; | ||||
|  | ||||
|             collectVariableRunTimes.ParallelForEach(variable => | ||||
|            { | ||||
|                variable.CollectDeviceRunTime = runtime; | ||||
|                variable.DeviceName = runtime.Name; | ||||
|            }); | ||||
|             return new() { runtime }; | ||||
|  | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     #region 导入导出 | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<MemoryStream> ExportFileAsync(CollectDeviceInput input) | ||||
|     { | ||||
|         var query = GetPage(input.Adapt<CollectDevicePageInput>()); | ||||
|         var data = await query.ToListAsync(); | ||||
|         return await ExportFileAsync(data); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("导出采集设备表", IsRecordPar = false)] | ||||
|     public async Task<MemoryStream> ExportFileAsync(List<CollectDevice> devDatas = null) | ||||
|     { | ||||
|         devDatas ??= GetCacheList(false); | ||||
|  | ||||
|         //总数据 | ||||
|         Dictionary<string, object> sheets = new(); | ||||
|         //设备页 | ||||
|         List<Dictionary<string, object>> devExports = new(); | ||||
|         //设备附加属性,转成Dict<表名,List<Dict<列名,列数据>>>的形式 | ||||
|         Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new(); | ||||
|         var driverPluginDicts = _driverPluginService.GetCacheList(false).ToDictionary(a => a.Id); | ||||
|         var deviceDicts = devDatas.ToDictionary(a => a.Id); | ||||
|         foreach (var devData in devDatas) | ||||
|         { | ||||
|             #region 设备sheet | ||||
|             //设备页 | ||||
|             var data = devData.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>() == null); | ||||
|             Dictionary<string, object> devExport = new(); | ||||
|             foreach (var item in data) | ||||
|             { | ||||
|                 //描述 | ||||
|                 var desc = item.FindDisplayAttribute(); | ||||
|                 //数据源增加 | ||||
|                 devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString()); | ||||
|             } | ||||
|             driverPluginDicts.TryGetValue(devData.PluginId, out var driverPlugin); | ||||
|             deviceDicts.TryGetValue(devData.RedundantDeviceId, out var redundantDevice); | ||||
|  | ||||
|             //设备实体没有包含插件名称,手动插入 | ||||
|             devExport.Add(ExportHelpers.PluginName, driverPlugin.AssembleName); | ||||
|             //设备实体没有包含冗余设备名称,手动插入 | ||||
|             devExport.Add(ExportHelpers.RedundantDeviceName, redundantDevice?.Name); | ||||
|  | ||||
|             //添加完整设备信息 | ||||
|             devExports.Add(devExport); | ||||
|  | ||||
|             #endregion | ||||
|  | ||||
|             #region 插件sheet | ||||
|             //插件属性 | ||||
|             //单个设备的行数据 | ||||
|             Dictionary<string, object> driverInfo = new(); | ||||
|             //没有包含设备名称,手动插入 | ||||
|             if (devData.DevicePropertys.Count > 0) | ||||
|             { | ||||
|                 driverInfo.Add(ExportHelpers.DeviceName, devData.Name); | ||||
|             } | ||||
|             foreach (var item in devData.DevicePropertys ?? new()) | ||||
|             { | ||||
|                 //添加对应属性数据 | ||||
|                 driverInfo.Add(item.Description, item.Value); | ||||
|             } | ||||
|  | ||||
|             //插件名称去除首部ThingsGateway.作为表名 | ||||
|             var pluginName = driverPlugin.AssembleName.Replace(ExportHelpers.PluginLeftName, ""); | ||||
|             if (devicePropertys.ContainsKey(pluginName)) | ||||
|             { | ||||
|                 if (driverInfo.Count > 0) | ||||
|                     devicePropertys[pluginName].Add(driverInfo); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (driverInfo.Count > 0) | ||||
|                     devicePropertys.Add(pluginName, new() { driverInfo }); | ||||
|             } | ||||
|  | ||||
|             #endregion | ||||
|         } | ||||
|  | ||||
|         //添加设备页 | ||||
|         sheets.Add(ExportHelpers.CollectDeviceSheetName, devExports); | ||||
|         //添加插件属性页 | ||||
|         foreach (var item in devicePropertys) | ||||
|         { | ||||
|             sheets.Add(item.Key, item.Value); | ||||
|         } | ||||
|  | ||||
|         var memoryStream = new MemoryStream(); | ||||
|         await memoryStream.SaveAsAsync(sheets); | ||||
|         return memoryStream; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file) | ||||
|     { | ||||
|         _fileService.ImportVerification(file); | ||||
|         using var fs = new MemoryStream(); | ||||
|         using var stream = file.OpenReadStream(512000000); | ||||
|         await stream.CopyToAsync(fs); | ||||
|         var sheetNames = MiniExcel.GetSheetNames(fs); | ||||
|         var deviceDicts = GetCacheList(false).ToDictionary(a => a.Name); | ||||
|         var pluginDicts = _driverPluginService.GetCacheList(false).ToDictionary(a => a.AssembleName); | ||||
|  | ||||
|         //导入检验结果 | ||||
|         Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new(); | ||||
|         //设备页 | ||||
|         ImportPreviewOutput<CollectDevice> deviceImportPreview = new(); | ||||
|         foreach (var sheetName in sheetNames) | ||||
|         { | ||||
|             //单页数据 | ||||
|             var rows = fs.Query(useHeaderRow: true, sheetName: sheetName).Cast<IDictionary<string, object>>(); | ||||
|             #region 采集设备sheet | ||||
|             if (sheetName == ExportHelpers.CollectDeviceSheetName) | ||||
|             { | ||||
|                 int row = 1; | ||||
|                 ImportPreviewOutput<CollectDevice> importPreviewOutput = new(); | ||||
|                 ImportPreviews.Add(sheetName, importPreviewOutput); | ||||
|                 deviceImportPreview = importPreviewOutput; | ||||
|                 List<CollectDevice> devices = new(); | ||||
|                 rows.ForEach(item => | ||||
|                { | ||||
|                    try | ||||
|                    { | ||||
|  | ||||
|                        var device = ((ExpandoObject)item).ConvertToEntity<CollectDevice>(true); | ||||
|                        #region 特殊转化名称 | ||||
|                        //转化插件名称 | ||||
|                        var hasPlugin = item.TryGetValue(ExportHelpers.PluginName, out var pluginObj); | ||||
|  | ||||
|                        if (pluginObj == null || !pluginDicts.TryGetValue(pluginObj.ToString(), out var plugin)) | ||||
|                        { | ||||
|                            //找不到对应的插件 | ||||
|                            importPreviewOutput.HasError = true; | ||||
|                            importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在")); | ||||
|                            return; | ||||
|                        } | ||||
|                        //转化冗余设备名称 | ||||
|                        var hasRedundant = item.TryGetValue(ExportHelpers.PluginName, out var redundantObj); | ||||
|  | ||||
|                        #endregion | ||||
|                        //插件ID、设备ID、冗余设备ID都需要手动补录 | ||||
|                        device.PluginId = plugin.Id; | ||||
|                        if (hasRedundant && deviceDicts.TryGetValue(redundantObj.ToString(), out var rendundantDevice)) | ||||
|                        { | ||||
|                            device.RedundantDeviceId = rendundantDevice.Id; | ||||
|                        } | ||||
|                        device.Id = deviceDicts.TryGetValue(device.Name, out var collectDevice) ? collectDevice.Id : YitIdHelper.NextId(); | ||||
|  | ||||
|                        devices.Add(device); | ||||
|                        importPreviewOutput.Results.Add((row++, true, "成功")); | ||||
|                        return; | ||||
|                    } | ||||
|                    catch (Exception ex) | ||||
|                    { | ||||
|  | ||||
|                        importPreviewOutput.HasError = true; | ||||
|                        importPreviewOutput.Results.Add((row++, false, ex.Message)); | ||||
|                        return; | ||||
|                    } | ||||
|                }); | ||||
|                 importPreviewOutput.Data = devices.ToDictionary(a => a.Name); | ||||
|  | ||||
|             } | ||||
|             #endregion | ||||
|             else | ||||
|             { | ||||
|                 int row = 1; | ||||
|                 ImportPreviewOutput<string> importPreviewOutput = new(); | ||||
|                 ImportPreviews.Add(sheetName, importPreviewOutput); | ||||
|                 //插件属性需加上前置名称 | ||||
|                 //var newName = ExportHelpers.PluginLeftName + sheetName; | ||||
|                 var newName = sheetName; | ||||
|                 var pluginId = _driverPluginService.GetIdByName(newName); | ||||
|                 if (pluginId == null) | ||||
|                 { | ||||
|                     importPreviewOutput.HasError = true; | ||||
|                     importPreviewOutput.Results.Add((row++, false, $"插件{newName}不存在")); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var driverPlugin = _driverPluginService.GetDriverPluginById(pluginId.Value); | ||||
|                 var pluginSingletonService = App.GetService<PluginSingletonService>(); | ||||
|                 var driver = (DriverBase)pluginSingletonService.GetDriver(driverPlugin); | ||||
|                 var propertys = driver.DriverPropertys.GetType().GetPropertiesWithCache() | ||||
|     .Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null) | ||||
|     .ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description)); | ||||
|                 rows.ForEach(item => | ||||
|                { | ||||
|                    try | ||||
|                    { | ||||
|  | ||||
|                        List<DependencyProperty> devices = new(); | ||||
|                        foreach (var keyValuePair in item) | ||||
|                        { | ||||
|                            if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo)) | ||||
|                            { | ||||
|                                devices.Add(new() | ||||
|                                { | ||||
|                                    PropertyName = propertyInfo.Name, | ||||
|                                    Description = keyValuePair.Key.ToString(), | ||||
|                                    Value = keyValuePair.Value?.ToString() | ||||
|                                }); | ||||
|                            } | ||||
|  | ||||
|                        } | ||||
|                        //转化插件名称 | ||||
|  | ||||
|                        var value = item[ExportHelpers.DeviceName]; | ||||
|  | ||||
|                        deviceImportPreview.Data[value.ToString()].DevicePropertys = devices; | ||||
|                        importPreviewOutput.Results.Add((row++, true, "成功")); | ||||
|                        return; | ||||
|                    } | ||||
|                    catch (Exception ex) | ||||
|                    { | ||||
|                        importPreviewOutput.HasError = true; | ||||
|                        importPreviewOutput.Results.Add((row++, false, ex.Message)); | ||||
|                        return; | ||||
|                    } | ||||
|                }); | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|  | ||||
|         return ImportPreviews; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("导入采集设备表", IsRecordPar = false)] | ||||
|     public async Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     { | ||||
|         var collectDevices = new List<CollectDevice>(); | ||||
|         foreach (var item in input) | ||||
|         { | ||||
|             if (item.Key == ExportHelpers.CollectDeviceSheetName) | ||||
|             { | ||||
|                 var collectDeviceImports = ((ImportPreviewOutput<CollectDevice>)item.Value).Data; | ||||
|                 collectDevices = collectDeviceImports.Values.Adapt<List<CollectDevice>>(); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         await Context.Storageable(collectDevices).ExecuteCommandAsync(); | ||||
|         CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_CollectDevice);//cache删除 | ||||
|  | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
| } | ||||
| @@ -1,88 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 采集设备服务 | ||||
| /// </summary> | ||||
| public interface ICollectDeviceService : ITransient | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 添加设备 | ||||
|     /// </summary> | ||||
|     Task AddAsync(CollectDevice input); | ||||
|     /// <summary> | ||||
|     /// 复制设备 | ||||
|     /// </summary> | ||||
|     Task CopyDevAsync(IEnumerable<CollectDevice> input); | ||||
|     /// <summary> | ||||
|     /// 复制设备与变量 | ||||
|     /// </summary> | ||||
|     Task CopyDevAndVarAsync(IEnumerable<CollectDevice> input); | ||||
|     /// <summary> | ||||
|     /// 删除设备 | ||||
|     /// </summary> | ||||
|     Task DeleteAsync(params long[] input); | ||||
|     /// <summary> | ||||
|     /// 编辑设备 | ||||
|     /// </summary> | ||||
|     Task EditAsync(CollectDeviceEditInput input); | ||||
|     /// <summary> | ||||
|     /// 导出Excel | ||||
|     /// </summary> | ||||
|     Task<MemoryStream> ExportFileAsync(List<CollectDevice> devDatas = null); | ||||
|     /// <summary> | ||||
|     /// 导出Excel | ||||
|     /// </summary> | ||||
|     Task<MemoryStream> ExportFileAsync(CollectDeviceInput input); | ||||
|     /// <summary> | ||||
|     /// 获取缓存 | ||||
|     /// </summary> | ||||
|     List<CollectDevice> GetCacheList(bool isMapster = true); | ||||
|     /// <summary> | ||||
|     /// 获取设备运行状态 | ||||
|     /// </summary> | ||||
|     Task<List<CollectDeviceRunTime>> GetCollectDeviceRuntimeAsync(long devId = 0); | ||||
|     /// <summary> | ||||
|     /// 根据ID获取设备 | ||||
|     /// </summary> | ||||
|     CollectDevice GetDeviceById(long Id); | ||||
|     /// <summary> | ||||
|     /// 根据名称获取ID | ||||
|     /// </summary> | ||||
|     long? GetIdByName(string name); | ||||
|     /// <summary> | ||||
|     /// 根据ID获取名称 | ||||
|     /// </summary> | ||||
|     string GetNameById(long id); | ||||
|     /// <summary> | ||||
|     /// 获取设备组或名称的树节点 | ||||
|     /// </summary> | ||||
|     List<DeviceTree> GetTree(); | ||||
|     /// <summary> | ||||
|     /// 导入 | ||||
|     /// </summary> | ||||
|     Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input); | ||||
|     /// <summary> | ||||
|     /// 分页查询 | ||||
|     /// </summary> | ||||
|     Task<SqlSugarPagedList<CollectDevice>> PageAsync(CollectDevicePageInput input); | ||||
|     /// <summary> | ||||
|     /// 导入验证 | ||||
|     /// </summary> | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file); | ||||
| } | ||||
| @@ -1,151 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
| using Furion.DependencyInjection; | ||||
| using Furion.FriendlyException; | ||||
|  | ||||
| using System.Data; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
|  | ||||
| using Yitter.IdGenerator; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <inheritdoc cref="IDriverPluginService"/> | ||||
| [Injection(Proxy = typeof(OperDispatchProxy))] | ||||
| public partial class DriverPluginService : DbRepository<DriverPlugin>, IDriverPluginService | ||||
| { | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("添加/更新插件")] | ||||
|     public async Task AddAsync(DriverPluginAddInput input) | ||||
|     { | ||||
|         var pluginService = App.GetService<PluginSingletonService>(); | ||||
|         var datas = await pluginService.TestAddDriverAsync(input); | ||||
|  | ||||
|         var driverPlugins = GetCacheList(); | ||||
|         foreach (var item in datas) | ||||
|         { | ||||
|             var data = driverPlugins.FirstOrDefault(a => a.AssembleName == item.AssembleName); | ||||
|             if (data != null) | ||||
|             { | ||||
|                 item.Id = data.Id; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 item.Id = YitIdHelper.NextId(); | ||||
|             } | ||||
|         } | ||||
|         var delete = driverPlugins.Where(a => a.FilePath == datas.FirstOrDefault()?.FilePath).ToList(); | ||||
|         //事务 | ||||
|         var result = await itenant.UseTranAsync(async () => | ||||
|         { | ||||
|             await Context.Deleteable(delete).ExecuteCommandAsync(); | ||||
|             await Context.Storageable(datas).ExecuteCommandAsync(); | ||||
|         }); | ||||
|         if (result.IsSuccess)//如果成功了 | ||||
|         { | ||||
|             CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_DriverPlugin);//cache删除 | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw Oops.Oh(result.ErrorMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public List<DriverPlugin> GetCacheList(bool isMapster = true) | ||||
|     { | ||||
|         //先从Cache拿 | ||||
|         var driverPlugins = CacheStatic.Cache.Get<List<DriverPlugin>>(ThingsGatewayCacheConst.Cache_DriverPlugin, isMapster); | ||||
|         if (driverPlugins == null) | ||||
|         { | ||||
|             driverPlugins = Context.Queryable<DriverPlugin>() | ||||
|             .Select((u) => new DriverPlugin { Id = u.Id.SelectAll() }) | ||||
|             .ToList(); | ||||
|             if (driverPlugins != null) | ||||
|             { | ||||
|                 //插入Cache | ||||
|                 CacheStatic.Cache.Set(ThingsGatewayCacheConst.Cache_DriverPlugin, driverPlugins, isMapster); | ||||
|             } | ||||
|         } | ||||
|         return driverPlugins; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public DriverPlugin GetDriverPluginById(long Id) | ||||
|     { | ||||
|         var data = GetCacheList(); | ||||
|         return data.FirstOrDefault(it => it.Id == Id); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public List<DriverPluginCategory> GetDriverPluginChildrenList(DriverEnum? driverTypeEnum = null) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         if (driverTypeEnum != null) | ||||
|         { | ||||
|             data = data.Where(a => a.DriverTypeEnum == driverTypeEnum).ToList(); | ||||
|         } | ||||
|         var driverPluginCategories = data.GroupBy(a => a.FileName).Select(it => | ||||
|          { | ||||
|              var childrens = new List<DriverPluginCategory>(); | ||||
|              foreach (var item in it) | ||||
|              { | ||||
|                  childrens.Add(new DriverPluginCategory | ||||
|                  { | ||||
|                      Id = item.Id, | ||||
|                      Name = item.AssembleName, | ||||
|                  } | ||||
|                  ); | ||||
|              } | ||||
|              return new DriverPluginCategory | ||||
|              { | ||||
|                  Id = YitIdHelper.NextId(), | ||||
|                  Name = it.Key, | ||||
|                  Children = childrens, | ||||
|              }; | ||||
|          }); | ||||
|         return driverPluginCategories.ToList(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public long? GetIdByName(string name) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         return data.FirstOrDefault(it => it.AssembleName == name)?.Id; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public string GetNameById(long id) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         return data.FirstOrDefault(it => it.Id == id)?.AssembleName; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<SqlSugarPagedList<DriverPlugin>> PageAsync(DriverPluginPageInput input) | ||||
|     { | ||||
|         var query = Context.Queryable<DriverPlugin>() | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.Name), u => u.AssembleName.Contains(input.Name))//根据关键字查询 | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.FileName), u => u.FileName.Contains(input.FileName));//根据关键字查询 | ||||
|         for (int i = 0; i < input.SortField.Count; i++) | ||||
|         { | ||||
|             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||
|         } | ||||
|         query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序 | ||||
|  | ||||
|         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||
|         return pageInfo; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,79 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 上传设备添加DTO | ||||
| /// </summary> | ||||
| public class UploadDeviceAddInput : UploadDeviceEditInput | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     [Required(ErrorMessage = "不能为空")] | ||||
|     public override string Name { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     [MinValue(1, ErrorMessage = "插件不能为空")] | ||||
|     public override long PluginId { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     public override bool IsLogOut { get; set; } = true; | ||||
|     /// <inheritdoc/> | ||||
|     public override bool Enable { get; set; } = true; | ||||
| } | ||||
| /// <summary> | ||||
| /// 上传设备修改DTO | ||||
| /// </summary> | ||||
| public class UploadDeviceEditInput : UploadDevice | ||||
| { | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [Required(ErrorMessage = "不能为空")] | ||||
|     public override string Name { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     [MinValue(1, ErrorMessage = "插件不能为空")] | ||||
|     public override long PluginId { get; set; } | ||||
|  | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 上传设备分页查询 | ||||
| /// </summary> | ||||
| public class UploadDevicePageInput : BasePageInput | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     [Description("设备名称")] | ||||
|     public string Name { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     [Description("插件名称")] | ||||
|     public string PluginName { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     [Description("设备组")] | ||||
|     public string DeviceGroup { get; set; } | ||||
| } | ||||
| /// <summary> | ||||
| /// 上传设备分页查询 | ||||
| /// </summary> | ||||
| public class UploadDeviceInput | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     [Description("设备名称")] | ||||
|     public string Name { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     [Description("插件名称")] | ||||
|     public string PluginName { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     [Description("设备组")] | ||||
|     public string DeviceGroup { get; set; } | ||||
| } | ||||
|  | ||||
| @@ -1,85 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 上传设备服务 | ||||
| /// </summary> | ||||
| public interface IUploadDeviceService : ITransient | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Sql连接对象 | ||||
|     /// </summary> | ||||
|     public ISqlSugarClient Context { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加上传设备 | ||||
|     /// </summary> | ||||
|     Task AddAsync(UploadDevice input); | ||||
|     /// <summary> | ||||
|     /// 复制设备 | ||||
|     /// </summary> | ||||
|     Task CopyDevAsync(IEnumerable<UploadDevice> input); | ||||
|     /// <summary> | ||||
|     /// 删除设备 | ||||
|     /// </summary> | ||||
|     Task DeleteAsync(params long[] input); | ||||
|     /// <summary> | ||||
|     /// 编辑设备 | ||||
|     /// </summary> | ||||
|     Task EditAsync(UploadDeviceEditInput input); | ||||
|     /// <summary> | ||||
|     /// 导出 | ||||
|     /// </summary> | ||||
|     Task<MemoryStream> ExportFileAsync(List<UploadDevice> devDatas = null); | ||||
|     /// <summary> | ||||
|     /// 导出 | ||||
|     /// </summary> | ||||
|     Task<MemoryStream> ExportFileAsync(UploadDeviceInput input); | ||||
|     /// <summary> | ||||
|     /// 获取缓存 | ||||
|     /// </summary> | ||||
|     List<UploadDevice> GetCacheList(bool isMapster = true); | ||||
|     /// <summary> | ||||
|     /// 根据ID获取设备 | ||||
|     /// </summary> | ||||
|     UploadDevice GetDeviceById(long Id); | ||||
|     /// <summary> | ||||
|     /// 根据名称获取ID | ||||
|     /// </summary> | ||||
|     long? GetIdByName(string name); | ||||
|     /// <summary> | ||||
|     /// 根据ID获取名称 | ||||
|     /// </summary> | ||||
|     string GetNameById(long id); | ||||
|     /// <summary> | ||||
|     /// 获取上传设备运行状态 | ||||
|     /// </summary> | ||||
|     List<UploadDeviceRunTime> GetUploadDeviceRuntime(long devId = 0); | ||||
|     /// <summary> | ||||
|     /// 导入 | ||||
|     /// </summary> | ||||
|     Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input); | ||||
|     /// <summary> | ||||
|     /// 分页 | ||||
|     /// </summary> | ||||
|     Task<SqlSugarPagedList<UploadDevice>> PageAsync(UploadDevicePageInput input); | ||||
|     /// <summary> | ||||
|     /// 导入验证 | ||||
|     /// </summary> | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file); | ||||
| } | ||||
| @@ -1,440 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion; | ||||
| using Furion.DependencyInjection; | ||||
| using Furion.FriendlyException; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| using MiniExcelLibs; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Dynamic; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
|  | ||||
| using Yitter.IdGenerator; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <inheritdoc cref="IUploadDeviceService"/> | ||||
| [Injection(Proxy = typeof(OperDispatchProxy))] | ||||
| public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceService | ||||
| { | ||||
|     private readonly IDriverPluginService _driverPluginService; | ||||
|     private readonly IFileService _fileService; | ||||
|  | ||||
|     /// <inheritdoc cref="IUploadDeviceService"/> | ||||
|     public UploadDeviceService( | ||||
|          IDriverPluginService driverPluginService, | ||||
|         IFileService fileService | ||||
|    ) | ||||
|     { | ||||
|         _fileService = fileService; | ||||
|         _driverPluginService = driverPluginService; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("添加上传设备")] | ||||
|     public async Task AddAsync(UploadDevice input) | ||||
|     { | ||||
|         var account_Id = GetIdByName(input.Name); | ||||
|         if (account_Id > 0) | ||||
|             throw Oops.Bah($"存在重复的名称:{input.Name}"); | ||||
|         await InsertAsync(input);//添加数据 | ||||
|         CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除 | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("复制上传设备")] | ||||
|     public async Task CopyDevAsync(IEnumerable<UploadDevice> input) | ||||
|     { | ||||
|         var newId = YitIdHelper.NextId(); | ||||
|         var newDevs = input.Adapt<List<UploadDevice>>(); | ||||
|         newDevs.ForEach(a => | ||||
|         { | ||||
|             a.Id = newId; | ||||
|             a.Name = "Copy-" + a.Name + "-" + newId.ToString(); | ||||
|         }); | ||||
|  | ||||
|         var result = await InsertRangeAsync(newDevs);//添加数据 | ||||
|         CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除 | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("删除上传设备")] | ||||
|     public async Task DeleteAsync(params long[] input) | ||||
|     { | ||||
|         //获取所有ID | ||||
|         if (input.Length > 0) | ||||
|         { | ||||
|             var result = await DeleteByIdsAsync(input.Cast<object>().ToArray()); | ||||
|             if (result) | ||||
|             { | ||||
|                 CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("编辑上传设备")] | ||||
|     public async Task EditAsync(UploadDeviceEditInput input) | ||||
|     { | ||||
|         var account_Id = GetIdByName(input.Name); | ||||
|         if (account_Id > 0 && account_Id != input.Id) | ||||
|             throw Oops.Bah($"存在重复的名称:{input.Name}"); | ||||
|  | ||||
|         if (await Context.Updateable(input.Adapt<UploadDevice>()).ExecuteCommandAsync() > 0)//修改数据 | ||||
|             CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除 | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc cref="IUploadDeviceService"/> | ||||
|     public List<UploadDevice> GetCacheList(bool isMapster = true) | ||||
|     { | ||||
|         //先从Cache拿 | ||||
|         var uploadDevice = CacheStatic.Cache.Get<List<UploadDevice>>(ThingsGatewayCacheConst.Cache_UploadDevice, isMapster); | ||||
|         if (uploadDevice == null) | ||||
|         { | ||||
|             uploadDevice = Context.Queryable<UploadDevice>().ToList(); | ||||
|             if (uploadDevice != null) | ||||
|             { | ||||
|                 //插入Cache | ||||
|                 CacheStatic.Cache.Set(ThingsGatewayCacheConst.Cache_UploadDevice, uploadDevice, isMapster); | ||||
|             } | ||||
|         } | ||||
|         return uploadDevice; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public UploadDevice GetDeviceById(long Id) | ||||
|     { | ||||
|         var data = GetCacheList(); | ||||
|         return data.FirstOrDefault(it => it.Id == Id); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public long? GetIdByName(string name) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         return data.FirstOrDefault(it => it.Name == name)?.Id; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public string GetNameById(long id) | ||||
|     { | ||||
|         var data = GetCacheList(false); | ||||
|         return data.FirstOrDefault(it => it.Id == id)?.Name; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public List<UploadDeviceRunTime> GetUploadDeviceRuntime(long devId = 0) | ||||
|     { | ||||
|         ConcurrentDictionary<long, DriverPlugin> driverPlugins = new(_driverPluginService.GetCacheList(false).ToDictionary(a => a.Id)); | ||||
|         if (devId == 0) | ||||
|         { | ||||
|             var devices = GetCacheList(false).Where(a => a.Enable).ToList(); | ||||
|             var runtime = devices.Adapt<List<UploadDeviceRunTime>>(); | ||||
|             foreach (var device in runtime) | ||||
|             { | ||||
|                 driverPlugins.TryGetValue(device.PluginId, out var driverPlugin); | ||||
|                 device.PluginName = driverPlugin?.AssembleName; | ||||
|             } | ||||
|             return runtime; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var devices = GetCacheList(false).Where(a => a.Enable).ToList(); | ||||
|             devices = devices.Where(it => it.Id == devId).ToList(); | ||||
|             var runtime = devices.Adapt<List<UploadDeviceRunTime>>(); | ||||
|             foreach (var device in runtime) | ||||
|             { | ||||
|                 driverPlugins.TryGetValue(device.PluginId, out var driverPlugin); | ||||
|                 device.PluginName = driverPlugin?.AssembleName; | ||||
|             } | ||||
|             return runtime; | ||||
|  | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<SqlSugarPagedList<UploadDevice>> PageAsync(UploadDevicePageInput input) | ||||
|     { | ||||
|         var query = GetPage(input); | ||||
|         var pageInfo = await query.ToPagedListAsync(input.Current, input.Size);//分页 | ||||
|         return pageInfo; | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     private ISugarQueryable<UploadDevice> GetPage(UploadDevicePageInput input) | ||||
|     { | ||||
|         long? pluginid = 0; | ||||
|         if (!string.IsNullOrEmpty(input.PluginName)) | ||||
|         { | ||||
|             pluginid = _driverPluginService.GetCacheList(false).FirstOrDefault(it => it.AssembleName.Contains(input.PluginName))?.Id; | ||||
|         } | ||||
|         var query = Context.Queryable<UploadDevice>() | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.Name), u => u.Name.Contains(input.Name)) | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.PluginName), u => u.PluginId == (pluginid ?? 0)) | ||||
|          .WhereIF(!string.IsNullOrEmpty(input.DeviceGroup), u => u.DeviceGroup == input.DeviceGroup); | ||||
|         for (int i = 0; i < input.SortField.Count; i++) | ||||
|         { | ||||
|             query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}"); | ||||
|         } | ||||
|         query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序 | ||||
|  | ||||
|         return query; | ||||
|     } | ||||
|     #region 导入导出 | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<MemoryStream> ExportFileAsync(UploadDeviceInput input) | ||||
|     { | ||||
|         var query = GetPage(input.Adapt<UploadDevicePageInput>()); | ||||
|         var data = await query.ToListAsync(); | ||||
|         return await ExportFileAsync(data); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("导出上传设备表", IsRecordPar = false)] | ||||
|     public async Task<MemoryStream> ExportFileAsync(List<UploadDevice> devDatas = null) | ||||
|     { | ||||
|         devDatas ??= GetCacheList(false); | ||||
|  | ||||
|         //总数据 | ||||
|         Dictionary<string, object> sheets = new(); | ||||
|         //设备页 | ||||
|         List<Dictionary<string, object>> devExports = new(); | ||||
|         //设备附加属性,转成Dict<表名,List<Dict<列名,列数据>>>的形式 | ||||
|         Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new(); | ||||
|  | ||||
|         foreach (var devData in devDatas) | ||||
|         { | ||||
|             #region 设备sheet | ||||
|             //设备页 | ||||
|             var data = devData.GetType().GetPropertiesWithCache().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>() == null); | ||||
|             Dictionary<string, object> devExport = new(); | ||||
|             foreach (var item in data) | ||||
|             { | ||||
|                 //描述 | ||||
|                 var desc = item.FindDisplayAttribute(); | ||||
|                 //数据源增加 | ||||
|                 devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString()); | ||||
|             } | ||||
|             //设备实体没有包含插件名称,手动插入 | ||||
|             devExport.Add(ExportHelpers.PluginName, _driverPluginService.GetNameById(devData.PluginId)); | ||||
|  | ||||
|             //添加完整设备信息 | ||||
|             devExports.Add(devExport); | ||||
|  | ||||
|             #endregion | ||||
|  | ||||
|             #region 插件sheet | ||||
|             //插件属性 | ||||
|             //单个设备的行数据 | ||||
|             Dictionary<string, object> driverInfo = new(); | ||||
|             //没有包含设备名称,手动插入 | ||||
|             if (devData.DevicePropertys.Count > 0) | ||||
|             { | ||||
|                 driverInfo.Add(ExportHelpers.DeviceName, devData.Name); | ||||
|             } | ||||
|             foreach (var item in devData.DevicePropertys ?? new()) | ||||
|             { | ||||
|                 //添加对应属性数据 | ||||
|                 driverInfo.Add(item.Description, item.Value); | ||||
|             } | ||||
|  | ||||
|             //插件名称去除首部ThingsGateway.作为表名 | ||||
|             var pluginName = _driverPluginService.GetNameById(devData.PluginId).Replace(ExportHelpers.PluginLeftName, ""); | ||||
|             if (devicePropertys.ContainsKey(pluginName)) | ||||
|             { | ||||
|                 if (driverInfo.Count > 0) | ||||
|                     devicePropertys[pluginName].Add(driverInfo); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (driverInfo.Count > 0) | ||||
|                     devicePropertys.Add(pluginName, new() { driverInfo }); | ||||
|             } | ||||
|  | ||||
|             #endregion | ||||
|         } | ||||
|  | ||||
|         //添加设备页 | ||||
|         sheets.Add(ExportHelpers.UploadDeviceSheetName, devExports); | ||||
|         //添加插件属性页 | ||||
|         foreach (var item in devicePropertys) | ||||
|         { | ||||
|             sheets.Add(item.Key, item.Value); | ||||
|         } | ||||
|  | ||||
|         var memoryStream = new MemoryStream(); | ||||
|         await memoryStream.SaveAsAsync(sheets); | ||||
|         return memoryStream; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("导入上传设备表", IsRecordPar = false)] | ||||
|     public async Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     { | ||||
|         var uploadDevices = new List<UploadDevice>(); | ||||
|         foreach (var item in input) | ||||
|         { | ||||
|             if (item.Key == ExportHelpers.UploadDeviceSheetName) | ||||
|             { | ||||
|                 var uploadDeviceImports = ((ImportPreviewOutput<UploadDevice>)item.Value).Data; | ||||
|                 uploadDevices = uploadDeviceImports.Values.Adapt<List<UploadDevice>>(); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         await Context.Storageable(uploadDevices).ExecuteCommandAsync(); | ||||
|         CacheStatic.Cache.Remove(ThingsGatewayCacheConst.Cache_UploadDevice);//cache删除 | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file) | ||||
|     { | ||||
|         _fileService.ImportVerification(file); | ||||
|         using var fs = new MemoryStream(); | ||||
|         using var stream = file.OpenReadStream(512000000); | ||||
|         await stream.CopyToAsync(fs); | ||||
|         var sheetNames = MiniExcel.GetSheetNames(fs); | ||||
|         var deviceDicts = GetCacheList(false).ToDictionary(a => a.Name); | ||||
|         var pluginDicts = _driverPluginService.GetCacheList(false).ToDictionary(a => a.AssembleName); | ||||
|  | ||||
|         //导入检验结果 | ||||
|         Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new(); | ||||
|         //设备页 | ||||
|         ImportPreviewOutput<UploadDevice> deviceImportPreview = new(); | ||||
|         foreach (var sheetName in sheetNames) | ||||
|         { | ||||
|             //单页数据 | ||||
|             var rows = (fs.Query(useHeaderRow: true, sheetName: sheetName)).Cast<IDictionary<string, object>>(); | ||||
|             #region 上传设备sheet | ||||
|             if (sheetName == ExportHelpers.UploadDeviceSheetName) | ||||
|             { | ||||
|                 int row = 1; | ||||
|                 ImportPreviewOutput<UploadDevice> importPreviewOutput = new(); | ||||
|                 ImportPreviews.Add(sheetName, importPreviewOutput); | ||||
|                 deviceImportPreview = importPreviewOutput; | ||||
|  | ||||
|                 List<UploadDevice> devices = new(); | ||||
|                 rows.ForEach(item => | ||||
|                { | ||||
|                    try | ||||
|                    { | ||||
|                        var device = ((ExpandoObject)item).ConvertToEntity<UploadDevice>(true); | ||||
|  | ||||
|                        #region 特殊转化名称 | ||||
|                        //转化插件名称 | ||||
|                        var hasPlugin = item.TryGetValue(ExportHelpers.PluginName, out var pluginObj); | ||||
|  | ||||
|                        if (pluginObj == null || !pluginDicts.TryGetValue(pluginObj.ToString(), out var plugin)) | ||||
|                        { | ||||
|                            //找不到对应的插件 | ||||
|                            importPreviewOutput.HasError = true; | ||||
|                            importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在")); | ||||
|                            return; | ||||
|                        } | ||||
|                        #endregion | ||||
|                        //插件ID、设备ID都需要手动补录 | ||||
|                        device.PluginId = plugin.Id; | ||||
|                        device.Id = deviceDicts.TryGetValue(device.Name, out var uploadDevice) ? uploadDevice.Id : YitIdHelper.NextId(); | ||||
|  | ||||
|                        devices.Add(device); | ||||
|                        importPreviewOutput.Results.Add((row++, true, "成功")); | ||||
|                        return; | ||||
|                    } | ||||
|                    catch (Exception ex) | ||||
|                    { | ||||
|                        importPreviewOutput.HasError = true; | ||||
|                        importPreviewOutput.Results.Add((row++, false, ex.Message)); | ||||
|                        return; | ||||
|                    } | ||||
|                }); | ||||
|                 importPreviewOutput.Data = devices.ToDictionary(a => a.Name); | ||||
|  | ||||
|             } | ||||
|             #endregion | ||||
|             else | ||||
|             { | ||||
|                 int row = 1; | ||||
|                 ImportPreviewOutput<string> importPreviewOutput = new(); | ||||
|                 ImportPreviews.Add(sheetName, importPreviewOutput); | ||||
|                 //插件属性需加上前置名称 | ||||
|                 //var newName = ExportHelpers.PluginLeftName + sheetName; | ||||
|                 var newName = sheetName; | ||||
|                 var pluginId = _driverPluginService.GetIdByName(newName); | ||||
|                 if (pluginId == null) | ||||
|                 { | ||||
|                     importPreviewOutput.HasError = true; | ||||
|                     importPreviewOutput.Results.Add((row++, false, $"插件{newName}不存在")); | ||||
|  | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var driverPlugin = _driverPluginService.GetDriverPluginById(pluginId.Value); | ||||
|                 var pluginSingletonService = App.GetService<PluginSingletonService>(); | ||||
|                 var driver = pluginSingletonService.GetDriver(driverPlugin); | ||||
|                 var propertys = driver.DriverPropertys.GetType().GetPropertiesWithCache() | ||||
|     .Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null) | ||||
|     .ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description)); | ||||
|                 rows.ForEach(item => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|  | ||||
|  | ||||
|                         List<DependencyProperty> devices = new(); | ||||
|                         foreach (var keyValuePair in item) | ||||
|                         { | ||||
|                             if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo)) | ||||
|                             { | ||||
|                                 devices.Add(new() | ||||
|                                 { | ||||
|                                     PropertyName = propertyInfo.Name, | ||||
|                                     Description = keyValuePair.Key.ToString(), | ||||
|                                     Value = keyValuePair.Value?.ToString() | ||||
|                                 }); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                         //转化设备名称 | ||||
|                         var value = item[ExportHelpers.DeviceName]; | ||||
|  | ||||
|                         deviceImportPreview.Data[value.ToString()].DevicePropertys = devices; | ||||
|                         importPreviewOutput.Results.Add((row++, true, "成功")); | ||||
|                         return; | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         importPreviewOutput.HasError = true; | ||||
|                         importPreviewOutput.Results.Add((row++, false, ex.Message)); | ||||
|                         return; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|  | ||||
|         return ImportPreviews; | ||||
|     } | ||||
|     #endregion | ||||
| } | ||||
| @@ -1,101 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.DependencyInjection; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 变量数据服务 | ||||
| /// </summary> | ||||
| public interface IVariableService : ITransient | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 添加变量 | ||||
|     /// </summary> | ||||
|     Task AddAsync(DeviceVariable input); | ||||
|     /// <summary> | ||||
|     /// 添加变量 | ||||
|     /// </summary> | ||||
|     Task AddBatchAsync(List<DeviceVariable> input); | ||||
|     /// <summary> | ||||
|     /// 清空设备变量 | ||||
|     /// </summary> | ||||
|     Task ClearDeviceVariableAsync(); | ||||
|     /// <summary> | ||||
|     /// 清空中间变量 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     Task ClearMemoryVariableAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 删除变量 | ||||
|     /// </summary> | ||||
|     Task DeleteAsync(params long[] input); | ||||
|     /// <summary> | ||||
|     /// 删除变量缓存 | ||||
|     /// </summary> | ||||
|     void DeleteVariableFromCache(); | ||||
|     /// <summary> | ||||
|     /// 编辑变量 | ||||
|     /// </summary> | ||||
|     Task EditAsync(DeviceVariable input); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导出 | ||||
|     /// </summary> | ||||
|     Task<MemoryStream> ExportFileAsync(List<DeviceVariable> collectDeviceVariables = null, string deviceName = null); | ||||
|     /// <summary> | ||||
|     /// 导出 | ||||
|     /// </summary> | ||||
|     Task<MemoryStream> ExportFileAsync(MemoryVariableInput input); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取变量运行状态 | ||||
|     /// </summary> | ||||
|     Task<List<DeviceVariableRunTime>> GetDeviceVariableRuntimeAsync(long devId = 0); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取中间变量运行态 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     Task<List<DeviceVariableRunTime>> GetMemoryVariableRuntimeAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导入 | ||||
|     /// </summary> | ||||
|     Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input); | ||||
|     /// <summary> | ||||
|     /// 导出 | ||||
|     /// </summary> | ||||
|     /// <param name="devDatas"></param> | ||||
|     /// <returns></returns> | ||||
|     Task<MemoryStream> MemoryVariableExportFileAsync(List<MemoryVariable> devDatas = null); | ||||
|     /// <summary> | ||||
|     /// 导入 | ||||
|     /// </summary> | ||||
|     /// <param name="file"></param> | ||||
|     /// <returns></returns> | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> MemoryVariablePreviewAsync(IBrowserFile file); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分页查询 | ||||
|     /// </summary> | ||||
|     Task<SqlSugarPagedList<DeviceVariable>> PageAsync(VariablePageInput input); | ||||
|     /// <summary> | ||||
|     /// 导入验证 | ||||
|     /// </summary> | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file); | ||||
|  | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <None Remove="SeedData\Json\driver_plugin.json" /> | ||||
| 	  <None Remove="SeedData\Json\gatewayopenapi_user.json" /> | ||||
| 	  <None Remove="SeedData\Json\gateway_relation.json" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 	  <Content Include="SeedData\Json\driver_plugin.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\gateway_config.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\gateway_resource.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\gatewayopenapi_user.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	  <Content Include="SeedData\Json\gateway_relation.json"> | ||||
| 	    <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
| 	  </Content> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="CodingSeb.ExpressionEvaluator" Version="1.4.39" /> | ||||
| 		<PackageReference Include="Hardware.Info" Version="11.1.1.1" /> | ||||
| 		<PackageReference Include="CS-Script" Version="4.8.1" /> | ||||
| 		<!--CS-Script与Furion冲突,直接安装覆盖版本--> | ||||
| 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.6.0" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| </Project> | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,683 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.Foundation.Extension.ConcurrentQueue; | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| using UAParser; | ||||
|  | ||||
| using Yitter.IdGenerator; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备采集报警后台服务 | ||||
| /// </summary> | ||||
| public class AlarmWorker : BackgroundService | ||||
| { | ||||
|     private readonly GlobalDeviceData _globalDeviceData; | ||||
|     private readonly ILogger<AlarmWorker> _logger; | ||||
|     /// <inheritdoc cref="AlarmWorker"/> | ||||
|     public AlarmWorker(ILogger<AlarmWorker> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         _globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 报警变化事件 | ||||
|     /// </summary> | ||||
|     public event VariableChangeEventHandler OnAlarmChanged; | ||||
|     /// <summary> | ||||
|     /// 设备状态变化事件 | ||||
|     /// </summary> | ||||
|     public event DelegateOnDeviceChanged OnDeviceStatusChanged; | ||||
|     /// <summary> | ||||
|     /// 实时报警列表 | ||||
|     /// </summary> | ||||
|     public ConcurrentList<DeviceVariableRunTime> RealAlarmDeviceVariables { get; set; } = new(); | ||||
|     /// <summary> | ||||
|     /// 服务状态 | ||||
|     /// </summary> | ||||
|     public OperResult StatuString { get; set; } = new OperResult("初始化"); | ||||
|     private ConcurrentQueue<DeviceVariableRunTime> DeviceVariables { get; set; } = new(); | ||||
|     private ConcurrentQueue<HistoryAlarm> HisAlarmDeviceVariables { get; set; } = new(); | ||||
|     /// <summary> | ||||
|     /// 获取数据库链接 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<OperResult<SqlSugarClient>> GetAlarmDbAsync() | ||||
|     { | ||||
|         var ConfigService = ServiceHelper.Services.GetService<IConfigService>(); | ||||
|         var alarmEnable = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base, ThingsGatewayConfigConst.Config_Alarm_Enable))?.ConfigValue?.ToBoolean(); | ||||
|         var alarmDbType = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base, ThingsGatewayConfigConst.Config_Alarm_DbType))?.ConfigValue; | ||||
|         var alarmConnstr = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base, ThingsGatewayConfigConst.Config_Alarm_ConnStr))?.ConfigValue; | ||||
|  | ||||
|         if (!(alarmEnable == true)) | ||||
|         { | ||||
|             return new OperResult<SqlSugarClient>("历史报警已配置为Disable"); | ||||
|         } | ||||
|  | ||||
|         var configureExternalServices = new ConfigureExternalServices | ||||
|         { | ||||
|             EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required | ||||
|             { | ||||
|                 if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) | ||||
|                     || (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null)) | ||||
|                     column.IsNullable = true; | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
|         DbType type = DbType.SqlServer; | ||||
|         if (!string.IsNullOrEmpty(alarmDbType)) | ||||
|         { | ||||
|             if (Enum.TryParse<DbType>(alarmDbType, ignoreCase: true, out var result)) | ||||
|             { | ||||
|                 type = result; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return new OperResult<SqlSugarClient>("数据库类型转换失败"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var sqlSugarClient = new SqlSugarClient(new ConnectionConfig() | ||||
|         { | ||||
|             ConnectionString = alarmConnstr,//连接字符串 | ||||
|             DbType = type,//数据库类型 | ||||
|             IsAutoCloseConnection = true, //不设成true要手动close | ||||
|             ConfigureExternalServices = configureExternalServices, | ||||
|             MoreSettings = new ConnMoreSettings | ||||
|             { | ||||
|                 SqlServerCodeFirstNvarchar = true,//设置默认nvarchar | ||||
|                 TableEnumIsString = true, | ||||
|  | ||||
|             }, | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         return OperResult.CreateSuccessResult(sqlSugarClient); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取bool报警类型 | ||||
|     /// </summary> | ||||
|     private static AlarmEnum GetBoolAlarmCode(DeviceVariableRunTime tag, out string limit, out string expressions, out string text) | ||||
|     { | ||||
|         limit = string.Empty; | ||||
|         expressions = string.Empty; | ||||
|         text = string.Empty; | ||||
|         if (tag.BoolCloseAlarmEnable && tag.Value.ToBoolean() == false) | ||||
|         { | ||||
|             limit = false.ToString(); | ||||
|             expressions = tag.BoolCloseRestrainExpressions; | ||||
|             text = tag.BoolCloseAlarmText; | ||||
|             return AlarmEnum.Close; | ||||
|         } | ||||
|         if (tag.BoolOpenAlarmEnable && tag.Value.ToBoolean() == true) | ||||
|         { | ||||
|             limit = true.ToString(); | ||||
|             expressions = tag.BoolOpenRestrainExpressions; | ||||
|             text = tag.BoolOpenAlarmText; | ||||
|             return AlarmEnum.Open; | ||||
|         } | ||||
|         return AlarmEnum.None; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取value报警类型 | ||||
|     /// </summary> | ||||
|     private static AlarmEnum GetDecimalAlarmDegree(DeviceVariableRunTime tag, out string limit, out string expressions, out string text) | ||||
|     { | ||||
|         limit = string.Empty; | ||||
|         expressions = string.Empty; | ||||
|         text = string.Empty; | ||||
|  | ||||
|         if (tag.HHAlarmEnable && tag.Value.ToDecimal() > tag.HHAlarmCode.ToDecimal()) | ||||
|         { | ||||
|             limit = tag.HHAlarmCode.ToString(); | ||||
|             expressions = tag.HHRestrainExpressions; | ||||
|             text = tag.HHAlarmText; | ||||
|             return AlarmEnum.HH; | ||||
|         } | ||||
|  | ||||
|         if (tag.HAlarmEnable && tag.Value.ToDecimal() > tag.HAlarmCode.ToDecimal()) | ||||
|         { | ||||
|             limit = tag.HAlarmCode.ToString(); | ||||
|             expressions = tag.HRestrainExpressions; | ||||
|             text = tag.HAlarmText; | ||||
|             return AlarmEnum.H; | ||||
|         } | ||||
|  | ||||
|         if (tag.LAlarmEnable && tag.Value.ToDecimal() < tag.LAlarmCode.ToDecimal()) | ||||
|         { | ||||
|             limit = tag.LAlarmCode.ToString(); | ||||
|             expressions = tag.LRestrainExpressions; | ||||
|             text = tag.LAlarmText; | ||||
|             return AlarmEnum.L; | ||||
|         } | ||||
|         if (tag.LLAlarmEnable && tag.Value.ToDecimal() < tag.LLAlarmCode.ToDecimal()) | ||||
|         { | ||||
|             limit = tag.LLAlarmCode.ToString(); | ||||
|             expressions = tag.LLRestrainExpressions; | ||||
|             text = tag.LLAlarmText; | ||||
|             return AlarmEnum.LL; | ||||
|         } | ||||
|         return AlarmEnum.None; | ||||
|     } | ||||
|     #region worker服务 | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken token) | ||||
|     { | ||||
|         _logger?.LogInformation("报警服务启动"); | ||||
|         await base.StartAsync(token); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken token) | ||||
|     { | ||||
|         _logger?.LogInformation("报警服务停止"); | ||||
|         return base.StopAsync(token); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await Task.Delay(5000, stoppingToken); | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 核心实现 | ||||
|     /// <summary> | ||||
|     /// 循环线程取消标识 | ||||
|     /// </summary> | ||||
|     public ConcurrentList<CancellationTokenSource> StoppingTokens = new(); | ||||
|     /// <summary> | ||||
|     /// 全部重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock restartLock = new(); | ||||
|  | ||||
|     private Task HisAlarmTask; | ||||
|     private bool IsExited; | ||||
|     private Task RealAlarmTask; | ||||
|     private CacheDb CacheDb { get; set; } | ||||
|     /// <summary> | ||||
|     /// 重启 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task RestartAsync() | ||||
|     { | ||||
|         await StopAsync(); | ||||
|         await StartAsync(); | ||||
|     } | ||||
|  | ||||
|     internal async Task StartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             foreach (var item in _globalDeviceData.CollectDevices) | ||||
|             { | ||||
|                 item.DeviceStatusChange += DeviceStatusChange; | ||||
|                 item.DeviceVariableRunTimes?.ForEach(v => { v.VariableCollectChange += DeviceVariableChange; }); | ||||
|             } | ||||
|             StoppingTokens.Add(new()); | ||||
|             await InitAsync(); | ||||
|             if (RealAlarmTask.Status == TaskStatus.Created) | ||||
|                 RealAlarmTask.Start(); | ||||
|             if (HisAlarmTask.Status == TaskStatus.Created) | ||||
|                 HisAlarmTask.Start(); | ||||
|             IsExited = false; | ||||
|  | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|     internal async Task StopAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             IsExited = true; | ||||
|  | ||||
|             foreach (var device in _globalDeviceData.CollectDevices) | ||||
|             { | ||||
|                 device.DeviceStatusChange -= DeviceStatusChange; | ||||
|                 device.DeviceVariableRunTimes?.ForEach(v => { v.VariableCollectChange -= DeviceVariableChange; }); | ||||
|             } | ||||
|  | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|             if (RealAlarmTask != null) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     _logger?.LogInformation($"实时报警线程停止中"); | ||||
|                     await RealAlarmTask.WaitAsync(TimeSpan.FromSeconds(10)); | ||||
|                     _logger?.LogInformation($"实时报警线程已停止"); | ||||
|                 } | ||||
|                 catch (ObjectDisposedException) | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                 catch (TimeoutException) | ||||
|                 { | ||||
|                     _logger?.LogWarning($"实时报警线程停止超时,已强制取消"); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger?.LogWarning(ex, "等待实时报警线程停止错误"); | ||||
|                 } | ||||
|  | ||||
|                 RealAlarmTask?.SafeDispose(); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (HisAlarmTask != null) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     _logger?.LogInformation($"历史报警线程停止中"); | ||||
|                     await HisAlarmTask.WaitAsync(TimeSpan.FromSeconds(10)); | ||||
|                     _logger?.LogInformation($"历史报警线程已停止"); | ||||
|                 } | ||||
|                 catch (ObjectDisposedException) | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                 catch (TimeoutException) | ||||
|                 { | ||||
|                     _logger?.LogWarning($"历史报警线程停止超时,已强制取消"); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger?.LogWarning(ex, "等待历史报警线程停止错误"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             HisAlarmTask?.SafeDispose(); | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.SafeDispose(); | ||||
|             } | ||||
|             StoppingTokens.Clear(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void AlarmAnalysis(DeviceVariableRunTime item) | ||||
|     { | ||||
|         string limit; | ||||
|         string ex; | ||||
|         string text; | ||||
|         AlarmEnum alarmEnum; | ||||
|         if (item.DataTypeEnum.GetSystemType() == typeof(bool)) | ||||
|         { | ||||
|             alarmEnum = GetBoolAlarmCode(item, out limit, out ex, out text); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             alarmEnum = GetDecimalAlarmDegree(item, out limit, out ex, out text); | ||||
|         } | ||||
|         if (alarmEnum == AlarmEnum.None) | ||||
|         { | ||||
|             //需恢复报警,如果存在的话 | ||||
|             AlarmChange(item, null, text, EventEnum.Finish, alarmEnum); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             //需更新报警,不管是否存在 | ||||
|             if (!string.IsNullOrEmpty(ex)) | ||||
|             { | ||||
|                 var data = ex.GetExpressionsResult(item.Value); | ||||
|                 if (data is bool result) | ||||
|                 { | ||||
|                     if (result) | ||||
|                     { | ||||
|                         AlarmChange(item, limit, text, EventEnum.Alarm, alarmEnum); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 AlarmChange(item, limit, text, EventEnum.Alarm, alarmEnum); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|     private void AlarmChange(DeviceVariableRunTime item, object limit, string text, EventEnum eventEnum, AlarmEnum alarmEnum) | ||||
|     { | ||||
|         if (eventEnum == EventEnum.Finish) | ||||
|         { | ||||
|             //实时报警没有找到的话直接返回 | ||||
|             if (!RealAlarmDeviceVariables.Any(it => it.Id == item.Id)) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         else if (eventEnum == EventEnum.Alarm) | ||||
|         { | ||||
|             var variable = RealAlarmDeviceVariables.FirstOrDefault(it => it.Id == item.Id); | ||||
|             if (variable != null) | ||||
|             { | ||||
|                 if (item.AlarmTypeEnum == alarmEnum) | ||||
|                     return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (eventEnum == EventEnum.Alarm) | ||||
|         { | ||||
|             item.AlarmTypeEnum = alarmEnum; | ||||
|             item.EventTypeEnum = eventEnum; | ||||
|             item.AlarmLimit = limit.ToString(); | ||||
|             item.AlarmCode = item.Value.ToString(); | ||||
|             item.AlarmText = text; | ||||
|             item.AlarmTime = SysDateTimeExtensions.CurrentDateTime; | ||||
|             item.EventTime = SysDateTimeExtensions.CurrentDateTime; | ||||
|         } | ||||
|         else if (eventEnum == EventEnum.Finish) | ||||
|         { | ||||
|             var oldAlarm = RealAlarmDeviceVariables.FirstOrDefault(it => it.Id == item.Id); | ||||
|             item.AlarmTypeEnum = oldAlarm.AlarmTypeEnum; | ||||
|             item.EventTypeEnum = eventEnum; | ||||
|             item.AlarmLimit = oldAlarm.AlarmLimit; | ||||
|             item.AlarmCode = item.Value.ToString(); | ||||
|             item.AlarmText = text; | ||||
|             item.EventTime = SysDateTimeExtensions.CurrentDateTime; | ||||
|         } | ||||
|         else if (eventEnum == EventEnum.Check) | ||||
|         { | ||||
|             item.AlarmTypeEnum = alarmEnum; | ||||
|             item.EventTypeEnum = eventEnum; | ||||
|             item.AlarmLimit = limit.ToString(); | ||||
|             item.AlarmCode = item.Value.ToString(); | ||||
|             item.AlarmText = text; | ||||
|             item.EventTime = SysDateTimeExtensions.CurrentDateTime; | ||||
|         } | ||||
|  | ||||
|         OnAlarmChanged?.Invoke(item.Adapt<DeviceVariableRunTime>()); | ||||
|         if (!IsExited) | ||||
|         { | ||||
|             HisAlarmDeviceVariables.Enqueue(item.Adapt<HistoryAlarm>()); | ||||
|         } | ||||
|  | ||||
|         if (eventEnum == EventEnum.Alarm) | ||||
|         { | ||||
|             RealAlarmDeviceVariables.RemoveWhere(it => it.Id == item.Id); | ||||
|             RealAlarmDeviceVariables.Add(item); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             RealAlarmDeviceVariables.RemoveWhere(it => it.Id == item.Id); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private void DeviceStatusChange(CollectDeviceRunTime device) | ||||
|     { | ||||
|         OnDeviceStatusChanged?.Invoke(device.Adapt<CollectDeviceRunTime>()); | ||||
|     } | ||||
|  | ||||
|     private void DeviceVariableChange(DeviceVariableRunTime variable) | ||||
|     { | ||||
|         //这里不能序列化变量,报警服务需改变同一个变量指向的属性 | ||||
|         DeviceVariables.Enqueue(variable); | ||||
|  | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     private async Task InitAsync() | ||||
|     { | ||||
|         CacheDb = new("HistoryAlarmCache"); | ||||
|         CancellationTokenSource stoppingToken = StoppingTokens.Last(); | ||||
|         RealAlarmTask = await Task.Factory.StartNew(async () => | ||||
|         { | ||||
|             _logger?.LogInformation($"实时报警线程开始"); | ||||
|             while (!stoppingToken.Token.IsCancellationRequested) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     await Task.Delay(500, stoppingToken.Token); | ||||
|                     var list = DeviceVariables.ToListWithDequeue(); | ||||
|                     foreach (var item in list) | ||||
|                     { | ||||
|                         if (stoppingToken.Token.IsCancellationRequested) | ||||
|                             break; | ||||
|                         if (!item.AlarmEnable) continue; | ||||
|                         AlarmAnalysis(item); | ||||
|                     } | ||||
|                     if (stoppingToken.Token.IsCancellationRequested) | ||||
|                         break; | ||||
|                 } | ||||
|                 catch (TaskCanceledException) | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                 catch (ObjectDisposedException) | ||||
|                 { | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger?.LogWarning(ex, $"实时报警循环异常"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  , TaskCreationOptions.LongRunning); | ||||
|  | ||||
|         HisAlarmTask = await Task.Factory.StartNew(async () => | ||||
|         { | ||||
|             _logger?.LogInformation($"历史报警线程开始"); | ||||
|             await Task.Yield();//返回线程控制,不再阻塞 | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(500, stoppingToken.Token); | ||||
|  | ||||
|                 var result = await GetAlarmDbAsync(); | ||||
|                 if (!result.IsSuccess) | ||||
|                 { | ||||
|                     _logger?.LogWarning($"历史报警线程即将退出:" + result.Message); | ||||
|                     StatuString = new OperResult($"已退出:{result.Message}"); | ||||
|                     IsExited = true; | ||||
|                     return; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     var sqlSugarClient = result.Content; | ||||
|                     bool isSuccess = true; | ||||
|                     /***创建/更新单个表***/ | ||||
|                     try | ||||
|                     { | ||||
|                         await sqlSugarClient.Queryable<HistoryAlarm>().FirstAsync(stoppingToken.Token); | ||||
|                         isSuccess = true; | ||||
|                         StatuString = OperResult.CreateSuccessResult(); | ||||
|                     } | ||||
|                     catch (Exception) | ||||
|                     { | ||||
|                         if (stoppingToken.Token.IsCancellationRequested) | ||||
|                         { | ||||
|                             IsExited = true; | ||||
|                             return; | ||||
|                         } | ||||
|                         try | ||||
|                         { | ||||
|                             _logger.LogWarning("连接历史报警表失败,尝试初始化表"); | ||||
|                             sqlSugarClient.CodeFirst.InitTables(typeof(HistoryAlarm)); | ||||
|                             isSuccess = true; | ||||
|                             StatuString = OperResult.CreateSuccessResult(); | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             isSuccess = false; | ||||
|                             StatuString = new OperResult(ex); | ||||
|                             _logger.LogWarning(ex, "连接历史报警数据库失败"); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     while (!stoppingToken.Token.IsCancellationRequested) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             await Task.Delay(500, stoppingToken.Token); | ||||
|  | ||||
|                             if (stoppingToken.Token.IsCancellationRequested) | ||||
|                                 break; | ||||
|  | ||||
|                             //缓存值 | ||||
|                             var cacheData = await CacheDb.GetCacheData(); | ||||
|                             if (cacheData.Count > 0) | ||||
|                             { | ||||
|                                 var data = cacheData.SelectMany(a => a.CacheStr.FromJson<List<HistoryAlarm>>()).ToList(); | ||||
|                                 try | ||||
|                                 { | ||||
|                                     var count = await sqlSugarClient.Insertable(data).ExecuteCommandAsync(stoppingToken.Token); | ||||
|                                     await CacheDb.DeleteCacheData(cacheData.Select(a => a.Id).ToArray()); | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     if (isSuccess) | ||||
|                                         _logger.LogWarning(ex, "写入历史报警失败"); | ||||
|                                 } | ||||
|                             } | ||||
|                             if (stoppingToken.Token.IsCancellationRequested) | ||||
|                                 break; | ||||
|  | ||||
|  | ||||
|                             var list = HisAlarmDeviceVariables.ToListWithDequeue(); | ||||
|                             if (list.Count != 0) | ||||
|                             { | ||||
|                                 ////Sql保存 | ||||
|                                 list.ForEach(it => | ||||
|                                 { | ||||
|                                     it.Id = YitIdHelper.NextId(); | ||||
|                                 }); | ||||
|                                 //插入 | ||||
|                                 try | ||||
|                                 { | ||||
|                                     await sqlSugarClient.Insertable(list).ExecuteCommandAsync(stoppingToken.Token); | ||||
|                                     isSuccess = true; | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     if (isSuccess) | ||||
|                                         _logger.LogWarning(ex, "写入历史报警失败"); | ||||
|  | ||||
|                                     var cacheDatas = list.ChunkTrivialBetter(500); | ||||
|                                     foreach (var a in cacheDatas) | ||||
|                                     { | ||||
|                                         await CacheDb.AddCacheData("", a.ToJson(), 50000); | ||||
|                                     } | ||||
|                                 } | ||||
|  | ||||
|                             } | ||||
|  | ||||
|  | ||||
|                         } | ||||
|                         catch (TaskCanceledException) | ||||
|                         { | ||||
|  | ||||
|                         } | ||||
|                         catch (ObjectDisposedException) | ||||
|                         { | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             if (isSuccess) | ||||
|                                 _logger?.LogWarning($"历史报警循环异常:" + ex.Message); | ||||
|                             StatuString = new OperResult(ex); | ||||
|                             isSuccess = false; | ||||
|                         } | ||||
|                     } | ||||
|                     IsExited = true; | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|                 IsExited = true; | ||||
|  | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|                 IsExited = true; | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 IsExited = true; | ||||
|                 _logger?.LogError($"历史报警异常:" + ex.Message); | ||||
|             } | ||||
|         } | ||||
|  , TaskCreationOptions.LongRunning); | ||||
|     } | ||||
|     #endregion | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,770 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.FriendlyException; | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.Foundation.Extension.Byte; | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备子线程服务 | ||||
| /// </summary> | ||||
| public class CollectDeviceCore | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 特殊方法变量 | ||||
|     /// </summary> | ||||
|     public List<DeviceVariableMethodSource> DeviceVariableMethodReads = new(); | ||||
|     /// <summary> | ||||
|     /// 特殊方法变量,不参与轮询执行 | ||||
|     /// </summary> | ||||
|     public List<DeviceVariableMethodSource> DeviceVariableMethodSources = new(); | ||||
|     /// <summary> | ||||
|     /// 变量打包 | ||||
|     /// </summary> | ||||
|     public List<DeviceVariableSourceRead> DeviceVariableSourceReads = new(); | ||||
|     /// <summary> | ||||
|     /// 全局插件服务 | ||||
|     /// </summary> | ||||
|     private readonly PluginSingletonService _pluginService; | ||||
|     /// <summary> | ||||
|     /// 读写锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock easyLock = new(); | ||||
|     /// <summary> | ||||
|     /// 当前设备信息 | ||||
|     /// </summary> | ||||
|     private CollectDeviceRunTime _device; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前的驱动插件实例 | ||||
|     /// </summary> | ||||
|     private CollectBase _driver; | ||||
|     /// <summary> | ||||
|     /// 日志 | ||||
|     /// </summary> | ||||
|     private ILogger _logger; | ||||
|     /// <summary> | ||||
|     /// 是否初始化成功 | ||||
|     /// </summary> | ||||
|     private bool isInitSuccess = true; | ||||
|  | ||||
|     /// <inheritdoc cref="CollectDeviceCore"/> | ||||
|     public CollectDeviceCore() | ||||
|     { | ||||
|         _pluginService = ServiceHelper.Services.GetService<PluginSingletonService>(); | ||||
|         GlobalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>(); | ||||
|         DriverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备 | ||||
|     /// </summary> | ||||
|     public CollectDeviceRunTime Device => _device; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备Id | ||||
|     /// </summary> | ||||
|     public long DeviceId => (long)(_device?.Id.ToLong()); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前插件 | ||||
|     /// </summary> | ||||
|     public CollectBase Driver => _driver; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化成功 | ||||
|     /// </summary> | ||||
|     public bool IsInitSuccess => isInitSuccess; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志 | ||||
|     /// </summary> | ||||
|     public ILogger Logger => _logger; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备全部特殊方法,执行初始化后获取正确值 | ||||
|     /// </summary> | ||||
|     public List<MethodInfo> Methods { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备全部设备属性,执行初始化后获取正确值 | ||||
|     /// </summary> | ||||
|     public List<DependencyProperty> Propertys { get; private set; } | ||||
|  | ||||
|     private IDriverPluginService DriverPluginService { get; set; } | ||||
|  | ||||
|     private GlobalDeviceData GlobalDeviceData { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 暂停采集 | ||||
|     /// </summary> | ||||
|     public void PasueThread(bool keepRun) | ||||
|     { | ||||
|         lock (this) | ||||
|         { | ||||
|             var str = keepRun == false ? "设备线程采集暂停" : "设备线程采集继续"; | ||||
|             _logger?.LogInformation($"{str}:{_device.Name}"); | ||||
|             this.Device.KeepRun = keepRun; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #region 插件处理 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取插件 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private CollectBase CreatDriver() | ||||
|     { | ||||
|  | ||||
|         var driverPlugin = DriverPluginService.GetDriverPluginById(_device.PluginId); | ||||
|         if (driverPlugin != null) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 _driver = (CollectBase)_pluginService.GetDriver(driverPlugin); | ||||
|                 if (_driver == null) | ||||
|                 { | ||||
|                     throw Oops.Oh($"创建插件失败"); | ||||
|                 } | ||||
|                 Methods = _pluginService.GetMethod(_driver); | ||||
|                 Propertys = _pluginService.GetDriverProperties(_driver); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 throw Oops.Oh($"创建插件失败:{ex.Message}"); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw Oops.Oh($"找不到驱动{driverPlugin?.AssembleName}"); | ||||
|         } | ||||
|         //设置插件配置项 | ||||
|         SetPluginProperties(_device.DevicePropertys); | ||||
|         return _driver; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private void InitDriver(object client) | ||||
|     { | ||||
|         //初始化插件 | ||||
|         _driver.Init(_logger, _device, client); | ||||
|         //变量打包 | ||||
|         LoadSourceReads(_device.DeviceVariableRunTimes); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置驱动插件的属性值 | ||||
|     /// </summary> | ||||
|     private void SetPluginProperties(List<DependencyProperty> deviceProperties) | ||||
|     { | ||||
|         if (deviceProperties == null) return; | ||||
|         _pluginService.SetDriverProperties(_driver, deviceProperties); | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 核心读写 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否多个设备共享链路,由外部传入 | ||||
|     /// </summary> | ||||
|     public bool IsShareChannel; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 线程开始时执行 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     internal async Task BeforeActionAsync(CancellationToken token, object client = null) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (_device == null) | ||||
|             { | ||||
|                 _logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null"); | ||||
|                 isInitSuccess = false; | ||||
|                 return; | ||||
|             } | ||||
|             if (_driver == null) | ||||
|             { | ||||
|                 _logger?.LogWarning(_device.Name + " - 插件不能为null"); | ||||
|                 isInitSuccess = false; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             _logger?.LogInformation($"{_device.Name}采集设备线程开始"); | ||||
|  | ||||
|             InitDriver(client); | ||||
|             Device.SourceVariableCount = DeviceVariableSourceReads.Count; | ||||
|             Device.MethodVariableCount = DeviceVariableMethodReads.Count; | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (Device.KeepRun == true) | ||||
|                 { | ||||
|                     await _driver.BeforStartAsync(token); | ||||
|                     Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, _device.Name); | ||||
|                 Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message); | ||||
|             } | ||||
|             isInitSuccess = true; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogError(ex, _device.Name); | ||||
|             isInitSuccess = false; | ||||
|             Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999, ex.Message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 结束后 | ||||
|     /// </summary> | ||||
|     internal async Task FinishActionAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _logger?.LogInformation($"{_device.Name}采集线程停止中"); | ||||
|             await _driver?.AfterStopAsync(); | ||||
|  | ||||
|             _driver?.SafeDispose(); | ||||
|             _logger?.LogInformation($"{_device.Name}采集线程已停止"); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogError(ex, $"{Device.Name} 释放失败"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isInitSuccess = false; | ||||
|             GlobalDeviceData.CollectDevices.RemoveWhere(it => it.Id == Device.Id); | ||||
|             easyLock.SafeDispose(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     internal bool Init(CollectDeviceRunTime device) | ||||
|     { | ||||
|         if (device == null) | ||||
|         { | ||||
|             _logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null"); | ||||
|             return false; | ||||
|         } | ||||
|         try | ||||
|         { | ||||
|             bool isUpDevice = Device != device; | ||||
|             _device = device; | ||||
|             _logger = ServiceHelper.Services.GetService<ILoggerFactory>().CreateLogger("采集设备:" + _device.Name); | ||||
|             //更新插件信息 | ||||
|             CreatDriver(); | ||||
|             //全局数据更新 | ||||
|             if (isUpDevice) | ||||
|             { | ||||
|                 GlobalDeviceData.CollectDevices.RemoveWhere(it => it.Id == device.Id); | ||||
|                 GlobalDeviceData.CollectDevices.Add(device); | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogError(ex, device.Name); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行一次读取 | ||||
|     /// </summary> | ||||
|     internal async Task<ThreadRunReturn> RunActionAsync(CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (_device == null) | ||||
|             { | ||||
|                 _logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null"); | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|             if (_driver == null) | ||||
|             { | ||||
|                 _logger?.LogWarning(_device.Name + " - 插件不能为null"); | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|  | ||||
|             if (Device.KeepRun == false) | ||||
|             { | ||||
|                 //采集暂停 | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|  | ||||
|             if (DeviceVariableSourceReads.Count == 0 && Device.DeviceVariableRunTimes.Where(a => string.IsNullOrEmpty(a.OtherMethod)).Any()) | ||||
|             { | ||||
|                 //无采集变量 | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|             if (token.IsCancellationRequested) | ||||
|                 return ThreadRunReturn.Break; | ||||
|  | ||||
|             int deviceMethodsVariableSuccessNum = 0; | ||||
|             int deviceMethodsVariableFailedNum = 0; | ||||
|             int deviceSourceVariableSuccessNum = 0; | ||||
|             int deviceSourceVariableFailedNum = 0; | ||||
|  | ||||
|             //支持读取 | ||||
|             if (_driver.IsSupportRequest) | ||||
|             { | ||||
|                 foreach (var deviceVariableSourceRead in DeviceVariableSourceReads) | ||||
|                 { | ||||
|                     if (Device?.KeepRun == false) | ||||
|                     { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     if (token.IsCancellationRequested) | ||||
|                         break; | ||||
|  | ||||
|                     //连读变量 | ||||
|                     if (deviceVariableSourceRead.CheckIfRequestAndUpdateTime(SysDateTimeExtensions.CurrentDateTime)) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             await easyLock.WaitAsync(); | ||||
|                             var read = await _driver.ReadSourceAsync(deviceVariableSourceRead, token); | ||||
|                             if (read != null && read.IsSuccess) | ||||
|                             { | ||||
|                                 _logger?.LogTrace(_device.Name + " - " + " - 采集[" + deviceVariableSourceRead.Address + " - " + deviceVariableSourceRead.Length + "] 数据成功" + read.Content?.ToHexString(" ")); | ||||
|                                 deviceVariableSourceRead.LastSuccess = true; | ||||
|                                 deviceSourceVariableSuccessNum += 1; | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 _logger?.LogWarning(_device.Name + " - " + " - 采集[" + deviceVariableSourceRead.Address + " -" + deviceVariableSourceRead.Length + "] 数据失败 - " + read?.Message); | ||||
|                                 deviceVariableSourceRead.LastSuccess = false; | ||||
|                                 deviceSourceVariableFailedNum += 1; | ||||
|                                 Device.SetDeviceStatus(null, Device.ErrorCount + deviceSourceVariableFailedNum, read?.Message); | ||||
|                             } | ||||
|                         } | ||||
|                         finally | ||||
|                         { | ||||
|                             easyLock.Release(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 foreach (var deviceVariableMethodRead in DeviceVariableMethodReads) | ||||
|                 { | ||||
|                     if (Device?.KeepRun == false) | ||||
|                         continue; | ||||
|                     if (token.IsCancellationRequested) | ||||
|                         break; | ||||
|  | ||||
|                     //连读变量 | ||||
|                     if (deviceVariableMethodRead.CheckIfRequestAndUpdateTime(SysDateTimeExtensions.CurrentDateTime)) | ||||
|                     { | ||||
|  | ||||
|                         var read = await InvokeMethodAsync(deviceVariableMethodRead, token); | ||||
|                         if (read.IsSuccess) | ||||
|                         { | ||||
|                             _logger?.LogTrace(_device.Name + "执行方法[" + deviceVariableMethodRead.MethodInfo.Name + "] 成功" + read.Content.ToJson()); | ||||
|                             deviceMethodsVariableSuccessNum += 1; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             _logger?.LogWarning(_device.Name + "执行方法[" + deviceVariableMethodRead.MethodInfo.Name + "] 失败" + read?.Message); | ||||
|                             deviceMethodsVariableFailedNum += 1; | ||||
|                             Device.SetDeviceStatus(null, Device.ErrorCount + deviceSourceVariableFailedNum, read.Message); | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 if (deviceMethodsVariableFailedNum == 0 && deviceSourceVariableFailedNum == 0 && (deviceMethodsVariableSuccessNum != 0 || deviceSourceVariableSuccessNum != 0)) | ||||
|                 { | ||||
|                     //只有成功读取一次,失败次数都会清零 | ||||
|                     Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 0); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (deviceMethodsVariableFailedNum == 0 && deviceSourceVariableFailedNum == 0 && deviceMethodsVariableSuccessNum == 0 && deviceSourceVariableSuccessNum == 0) | ||||
|                     { | ||||
|                         //这次没有执行读取 | ||||
|                         //判断是否已连接 | ||||
|                         if (_driver.IsConnected()) | ||||
|                         { | ||||
|                             //更新设备活动时间 | ||||
|                             Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else //插件自更新设备状态与变量状态 | ||||
|             { | ||||
|                 //获取设备连接状态 | ||||
|                 if (_driver.IsConnected()) | ||||
|                 { | ||||
|                     //更新设备活动时间 | ||||
|                     Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 0); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (token.IsCancellationRequested) | ||||
|                 return ThreadRunReturn.Break; | ||||
|  | ||||
|             //正常返回None | ||||
|             return ThreadRunReturn.None; | ||||
|  | ||||
|         } | ||||
|         catch (TaskCanceledException) | ||||
|         { | ||||
|             return ThreadRunReturn.Break; | ||||
|         } | ||||
|         catch (ObjectDisposedException) | ||||
|         { | ||||
|             return ThreadRunReturn.Break; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogWarning(ex, $"采集线程循环异常{_device.Name}"); | ||||
|             Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message); | ||||
|             return ThreadRunReturn.None; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取设备变量打包列表/特殊方法列表 | ||||
|     /// </summary> | ||||
|     /// <param name="collectVariableRunTimes"></param> | ||||
|     private void LoadSourceReads(List<DeviceVariableRunTime> collectVariableRunTimes) | ||||
|     { | ||||
|         if (_device == null) | ||||
|         { | ||||
|             _logger?.LogError(nameof(CollectDeviceRunTime) + "设备不能为null"); | ||||
|             return; | ||||
|         } | ||||
|         if (_driver == null) | ||||
|         { | ||||
|             _logger?.LogWarning(_device.Name + " - 插件不能为null"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             //连读打包 | ||||
|             var tags = collectVariableRunTimes.Where(it => | ||||
|             it.ProtectTypeEnum != ProtectTypeEnum.WriteOnly && | ||||
|             string.IsNullOrEmpty(it.OtherMethod) | ||||
|             && !string.IsNullOrEmpty(it.VariableAddress)).ToList(); | ||||
|             DeviceVariableSourceReads = _driver.LoadSourceRead(tags); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             throw new($"连读打包失败,请查看变量地址是否正确", ex); | ||||
|         } | ||||
|         var variablesMethod = collectVariableRunTimes.Where(it => !string.IsNullOrEmpty(it.OtherMethod)); | ||||
|         { | ||||
|             var tag = variablesMethod.Where(it => it.ProtectTypeEnum != ProtectTypeEnum.WriteOnly); | ||||
|             List<DeviceVariableMethodSource> variablesMethodResult1 = GetMethod(tag, true); | ||||
|             DeviceVariableMethodReads = variablesMethodResult1; | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             var tag = variablesMethod.Where(it => it.ProtectTypeEnum != ProtectTypeEnum.ReadOnly); | ||||
|             List<DeviceVariableMethodSource> variablesMethodResult2 = GetMethod(tag, false); | ||||
|             DeviceVariableMethodSources = variablesMethodResult2; | ||||
|         } | ||||
|  | ||||
|         List<DeviceVariableMethodSource> GetMethod(IEnumerable<DeviceVariableRunTime> tag, bool isRead) | ||||
|         { | ||||
|             var variablesMethodResult = new List<DeviceVariableMethodSource>(); | ||||
|             foreach (var item in tag) | ||||
|             { | ||||
|                 var methodResult = new DeviceVariableMethodSource(item.IntervalTime); | ||||
|                 var method = Methods.FirstOrDefault(it => it.Name == item.OtherMethod); | ||||
|                 if (method != null) | ||||
|                 { | ||||
|                     methodResult.MethodInfo = new Method(method); | ||||
|                     methodResult.MethodStr = item.VariableAddress; | ||||
|  | ||||
|                     if (isRead) | ||||
|                     { | ||||
|                         //获取实际执行的参数列表 | ||||
|                         var ps = methodResult.MethodInfo.Info.GetParameters(); | ||||
|                         methodResult.MethodObj = new object[ps.Length]; | ||||
|  | ||||
|                         if (!string.IsNullOrEmpty(methodResult.MethodStr)) | ||||
|                         { | ||||
|                             string[] strs = methodResult.MethodStr?.Trim()?.Split(';'); | ||||
|                             try | ||||
|                             { | ||||
|                                 int index = 0; | ||||
|                                 for (int i = 0; i < ps.Length; i++) | ||||
|                                 { | ||||
|                                     if (typeof(CancellationToken).IsAssignableFrom(ps[i].ParameterType)) | ||||
|                                     { | ||||
|                                         methodResult.HasTokenObj = true; | ||||
|                                     } | ||||
|                                     else | ||||
|                                     { | ||||
|                                         //得到对于的方法参数值 | ||||
|                                         methodResult.MethodObj[i] = methodResult.Converter.ConvertFrom(strs[index], ps[i].ParameterType); | ||||
|                                         index++; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             catch (Exception ex) | ||||
|                             { | ||||
|                                 throw new($"特殊方法初始化失败,请查看变量地址/传入参数是否正确", ex); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|  | ||||
|                     methodResult.DeviceVariable = item; | ||||
|                     variablesMethodResult.Add(methodResult); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return variablesMethodResult; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
|     #region 写入与方法 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行特殊方法 | ||||
|     /// </summary> | ||||
|     internal async Task<OperResult<string>> InvokeMethodAsync(DeviceVariableMethodSource deviceVariableMethodSource, bool isRead, string value, CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|             OperResult<string> result = new(); | ||||
|             var method = deviceVariableMethodSource.MethodInfo; | ||||
|             if (method == null) | ||||
|             { | ||||
|                 result.ResultCode = ResultCode.Error; | ||||
|                 result.Message = $"{deviceVariableMethodSource.DeviceVariable.Name}找不到执行方法{deviceVariableMethodSource.DeviceVariable.OtherMethod}"; | ||||
|                 return result; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|  | ||||
|                 if (!isRead) | ||||
|                 { | ||||
|                     //获取执行参数 | ||||
|                     var ps = method.Info.GetParameters(); | ||||
|                     deviceVariableMethodSource.MethodObj = new object[ps.Length]; | ||||
|  | ||||
|                     if (!string.IsNullOrEmpty(deviceVariableMethodSource.MethodStr) || !string.IsNullOrEmpty(value)) | ||||
|                     { | ||||
|                         string[] strs1 = deviceVariableMethodSource.MethodStr?.Trim()?.Split(';'); | ||||
|                         string[] strs2 = value?.Trim()?.Split(';'); | ||||
|                         //通过分号分割,并且合并参数 | ||||
|                         var strs = strs1?.SpliceArray(strs2); | ||||
|                         int index = 0; | ||||
|                         for (int i = 0; i < ps.Length; i++) | ||||
|                         { | ||||
|                             if (typeof(CancellationToken).IsAssignableFrom(ps[i].ParameterType)) | ||||
|                             { | ||||
|                                 deviceVariableMethodSource.MethodObj[i] = token; | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 //得到对于的方法参数值 | ||||
|                                 deviceVariableMethodSource.MethodObj[i] = deviceVariableMethodSource.Converter.ConvertFrom(strs[index], ps[i].ParameterType); | ||||
|                                 index++; | ||||
|                                 if (strs.Length <= i) | ||||
|                                 { | ||||
|                                     result.ResultCode = ResultCode.Error; | ||||
|                                     result.Message = $"{deviceVariableMethodSource.DeviceVariable.Name} 执行方法 {deviceVariableMethodSource.DeviceVariable.OtherMethod} 参数不足{deviceVariableMethodSource.MethodStr}"; | ||||
|                                     //参数数量不符 | ||||
|                                     return result; | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 else if (deviceVariableMethodSource.HasTokenObj) | ||||
|                 { | ||||
|                     //获取执行参数 | ||||
|                     var ps = method.Info.GetParameters(); | ||||
|                     var newObjs = deviceVariableMethodSource.MethodObj.ToList(); | ||||
|                     if (!string.IsNullOrEmpty(deviceVariableMethodSource.MethodStr) || !string.IsNullOrEmpty(value)) | ||||
|                     { | ||||
|                         for (int i = 0; i < ps.Length; i++) | ||||
|                         { | ||||
|                             if (typeof(CancellationToken).IsAssignableFrom(ps[i].ParameterType)) | ||||
|                             { | ||||
|                                 newObjs.Insert(i, token); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     deviceVariableMethodSource.MethodObj = newObjs.ToArray(); | ||||
|                 } | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     object data = null; | ||||
|                     switch (method.TaskType) | ||||
|                     { | ||||
|                         case TaskReturnType.None: | ||||
|                             data = method.Invoke(_driver, deviceVariableMethodSource.MethodObj); | ||||
|                             break; | ||||
|                         case TaskReturnType.Task: | ||||
|                             await method.InvokeAsync(_driver, deviceVariableMethodSource.MethodObj); | ||||
|                             break; | ||||
|                         case TaskReturnType.TaskObject: | ||||
|                             //执行方法 | ||||
|                             data = await method.InvokeObjectAsync(_driver, deviceVariableMethodSource.MethodObj); | ||||
|                             break; | ||||
|                     } | ||||
|  | ||||
|                     result = data?.Adapt<OperResult<string>>(); | ||||
|                     if (method.HasReturn && result != null && result.IsSuccess) | ||||
|                     { | ||||
|                         var content = deviceVariableMethodSource.Converter.ConvertTo(result.Content?.ToString()?.Replace($"\0", "")); | ||||
|                         var operResult = deviceVariableMethodSource.DeviceVariable.SetValue(content); | ||||
|                         if (!operResult.IsSuccess) | ||||
|                         { | ||||
|                             _logger?.LogWarning(operResult.Message, ToString()); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         var operResult = deviceVariableMethodSource.DeviceVariable.SetValue(null); | ||||
|                         if (!operResult.IsSuccess) | ||||
|                         { | ||||
|                             _logger?.LogWarning(operResult.Message, ToString()); | ||||
|                         } | ||||
|                     } | ||||
|                     return result; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     result.ResultCode = ResultCode.Error; | ||||
|                     result.Message = $"{deviceVariableMethodSource.DeviceVariable.Name}执行{deviceVariableMethodSource.DeviceVariable.OtherMethod} 方法失败:{ex.Message}"; | ||||
|                     return result; | ||||
|                 } | ||||
|  | ||||
|  | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return (new OperResult<string>(ex)); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行变量写入 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     internal async Task<OperResult> InVokeWriteAsync(DeviceVariableRunTime deviceVariable, JToken value, CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|             if (IsShareChannel) _driver.InitDataAdapter(); | ||||
|  | ||||
|             if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions)) | ||||
|             { | ||||
|                 var jToken = value; | ||||
|                 object rawdata; | ||||
|                 if (jToken is JValue jValue) | ||||
|                 { | ||||
|                     rawdata = jValue.Value; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     rawdata = jToken.ToString(); | ||||
|                 } | ||||
|                 object data; | ||||
|                 try | ||||
|                 { | ||||
|                     data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata); | ||||
|                     var result = await _driver.WriteValueAsync(deviceVariable, JToken.FromObject(data), token); | ||||
|                     return result; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     return new OperResult(deviceVariable.Name + " 转换写入表达式失败:" + ex.Message); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var result = await _driver.WriteValueAsync(deviceVariable, value, token); | ||||
|                 return result; | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return (new OperResult(ex)); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行轮询特殊方法,并设置变量值 | ||||
|     /// </summary> | ||||
|     private async Task<OperResult<string>> InvokeMethodAsync(DeviceVariableMethodSource deviceVariableMethodRead, CancellationToken token) | ||||
|     { | ||||
|         var data = await InvokeMethodAsync(deviceVariableMethodRead, true, string.Empty, token); | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,255 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 采集设备线程管理 | ||||
| /// </summary> | ||||
| public class CollectDeviceThread : IAsyncDisposable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 链路标识 | ||||
|     /// </summary> | ||||
|     public readonly string ChangelID; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// CancellationTokenSources | ||||
|     /// </summary> | ||||
|     public ConcurrentList<CancellationTokenSource> StoppingTokens = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 线程 | ||||
|     /// </summary> | ||||
|     protected Task DeviceTask; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 启停锁 | ||||
|     /// </summary> | ||||
|     protected EasyLock easyLock = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     public CollectDeviceThread(string changelID) | ||||
|     { | ||||
|         ChangelID = changelID; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 默认等待间隔时间 | ||||
|     /// </summary> | ||||
|     public static int CycleInterval { get; } = 10; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 采集设备List,在CollectDeviceThread开始前应该初始化内容 | ||||
|     /// </summary> | ||||
|     public ConcurrentList<CollectDeviceCore> CollectDeviceCores { get; private set; } = new(); | ||||
|     /// <inheritdoc/> | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         await StopThreadAsync(); | ||||
|         easyLock.SafeDispose(); | ||||
|         CollectDeviceCores.Clear(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始采集 | ||||
|     /// </summary> | ||||
|     public virtual async Task StartThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             StoppingTokens.Add(new()); | ||||
|             //初始化采集线程 | ||||
|             await InitTaskAsync(); | ||||
|             if (DeviceTask.Status == TaskStatus.Created) | ||||
|                 DeviceTask?.Start(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 停止采集前,提前取消Token | ||||
|     /// </summary> | ||||
|     public virtual async Task BeforeStopThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             if (DeviceTask == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 停止采集 | ||||
|     /// </summary> | ||||
|     public virtual async Task StopThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             if (DeviceTask == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|             try | ||||
|             { | ||||
|                 await DeviceTask.WaitAsync(TimeSpan.FromSeconds(10)); | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (TimeoutException) | ||||
|             { | ||||
|                 foreach (var device in CollectDeviceCores) | ||||
|                 { | ||||
|                     device.Logger?.LogInformation($"{device.Device.Name}采集线程停止超时,已强制取消"); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 CollectDeviceCores.FirstOrDefault()?.Logger?.LogError(ex, $"{CollectDeviceCores.FirstOrDefault()?.Device?.Name}采集线程停止错误"); | ||||
|             } | ||||
|             foreach (CancellationTokenSource token in StoppingTokens) | ||||
|             { | ||||
|                 token?.SafeDispose(); | ||||
|             } | ||||
|             DeviceTask?.SafeDispose(); | ||||
|             DeviceTask = null; | ||||
|             StoppingTokens.Clear(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     protected async Task InitTaskAsync() | ||||
|     { | ||||
|         CancellationTokenSource stoppingToken = StoppingTokens.Last(); | ||||
|         DeviceTask = await Task.Factory.StartNew(async () => | ||||
|         { | ||||
|             var channelResult = CollectDeviceCores.FirstOrDefault().Driver.GetShareChannel(); | ||||
|             LoggerGroup log = CollectDeviceCores.FirstOrDefault().Driver.LogMessage; | ||||
|             foreach (var device in CollectDeviceCores) | ||||
|             { | ||||
|                 if (device.Driver == null) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 //添加通道报文到每个设备 | ||||
|                 var data = new EasyLogger(device.Driver.NewMessage) { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|                 log.AddLogger(data); | ||||
|                 //传入是否共享通道 | ||||
|                 device.IsShareChannel = CollectDeviceCores.Count > 1; | ||||
|                 if (channelResult.IsSuccess) | ||||
|                 { | ||||
|                     await device.BeforeActionAsync(stoppingToken.Token, channelResult.Content); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     await device.BeforeActionAsync(stoppingToken.Token); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             while (!stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 foreach (var device in CollectDeviceCores) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         if (stoppingToken.IsCancellationRequested) | ||||
|                             break; | ||||
|                         //初始化成功才能执行 | ||||
|                         if (device.IsInitSuccess) | ||||
|                         { | ||||
|                             //如果是共享通道类型,需要每次转换时切换适配器 | ||||
|                             if (device.IsShareChannel) device.Driver.InitDataAdapter(); | ||||
|  | ||||
|                             var result = await device.RunActionAsync(stoppingToken.Token); | ||||
|                             if (result == ThreadRunReturn.None) | ||||
|                             { | ||||
|                                 await Task.Delay(CycleInterval); | ||||
|                             } | ||||
|                             else if (result == ThreadRunReturn.Continue) | ||||
|                             { | ||||
|                                 await Task.Delay(1000); | ||||
|                             } | ||||
|                             else if (result == ThreadRunReturn.Break) | ||||
|                             { | ||||
|                                 //当线程返回Break,直接跳出循环 | ||||
|                                 break; | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             await Task.Delay(1000, stoppingToken.Token); | ||||
|                         } | ||||
|                     } | ||||
|                     catch (TaskCanceledException) | ||||
|                     { | ||||
|  | ||||
|                     } | ||||
|                     catch (ObjectDisposedException) | ||||
|                     { | ||||
|  | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         log.Exception(ex); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             //注意插件结束函数不能使用取消传播作为条件 | ||||
|             foreach (var device in CollectDeviceCores) | ||||
|             { | ||||
|                 //如果插件还没释放,执行一次结束函数 | ||||
|                 if (!device.Driver.DisposedValue) | ||||
|                     await device.FinishActionAsync(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  , TaskCreationOptions.LongRunning); | ||||
|     } | ||||
| } | ||||
| @@ -1,567 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.FriendlyException; | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备采集后台服务 | ||||
| /// </summary> | ||||
| public class CollectDeviceWorker : BackgroundService | ||||
| { | ||||
|     private readonly ICollectDeviceService _collectDeviceService; | ||||
|     private readonly ILogger<CollectDeviceWorker> _logger; | ||||
|     private readonly PluginSingletonService _pluginService; | ||||
|     /// <inheritdoc/> | ||||
|     public CollectDeviceWorker(ILogger<CollectDeviceWorker> logger, IServiceProvider serviceProvider) | ||||
|     { | ||||
|         ServiceHelper.Services = serviceProvider; | ||||
|         _logger = logger; | ||||
|         _pluginService = ServiceHelper.Services.GetService<PluginSingletonService>(); | ||||
|         _collectDeviceService = ServiceHelper.Services.GetService<ICollectDeviceService>(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 读取未停止的采集设备List | ||||
|     /// </summary> | ||||
|     public List<CollectDeviceCore> CollectDeviceCores => CollectDeviceThreads | ||||
|         .Where(a => !a.StoppingTokens.Any(b => b.IsCancellationRequested)) | ||||
|         .SelectMany(a => a.CollectDeviceCores).ToList(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备子线程列表 | ||||
|     /// </summary> | ||||
|     private ConcurrentList<CollectDeviceThread> CollectDeviceThreads { get; set; } = new(); | ||||
|  | ||||
|     #region 设备创建更新结束 | ||||
|     /// <summary> | ||||
|     /// 全部重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock restartLock = new(); | ||||
|     /// <summary> | ||||
|     /// 单个重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock singleRestartLock = new(); | ||||
|     /// <summary> | ||||
|     /// 控制设备线程启停 | ||||
|     /// </summary> | ||||
|     /// <param name="deviceId">传入0时全部设备都会执行</param> | ||||
|     /// <param name="isStart"></param> | ||||
|     /// <returns></returns> | ||||
|     public void ConfigDeviceThread(long deviceId, bool isStart) | ||||
|     { | ||||
|         if (deviceId == 0) | ||||
|             CollectDeviceCores.ForEach(a => a.PasueThread(isStart)); | ||||
|         else | ||||
|             CollectDeviceCores.FirstOrDefault(it => it.DeviceId == deviceId)?.PasueThread(isStart); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 重启采集服务 | ||||
|     /// </summary> | ||||
|     public async Task RestartDeviceThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             //停止其他后台服务 | ||||
|             await StopOtherHostService(); | ||||
|             //停止全部采集线程 | ||||
|             await RemoveAllDeviceThreadAsync(); | ||||
|             //创建全部采集线程 | ||||
|             await CreatAllDeviceThreadsAsync(); | ||||
|             //开始全部采集线程 | ||||
|             await StartAllDeviceThreadsAsync(); | ||||
|             //开始其他后台服务 | ||||
|             await StartOtherHostService(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 更新设备线程,切换为冗余通道 | ||||
|     /// </summary> | ||||
|     public async Task UpDeviceRedundantThreadAsync(long devId) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (singleRestartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|  | ||||
|             if (!_stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 var devThread = CollectDeviceThreads.FirstOrDefault(it => it.CollectDeviceCores.Any(a => a.DeviceId == devId)); | ||||
|                 var devCore = devThread.CollectDeviceCores.FirstOrDefault(a => a.DeviceId == devId); | ||||
|                 if (devThread == null) { throw Oops.Bah($"更新设备线程失败,不存在{devId}为id的设备"); } | ||||
|                 //这里先停止采集,操作会使线程取消,需要重新恢复线程 | ||||
|                 await devThread.StopThreadAsync(); | ||||
|  | ||||
|                 var dev = devCore.Device; | ||||
|  | ||||
|                 if (dev.IsRedundant) | ||||
|                 { | ||||
|                     if (dev.Redundant == RedundantEnum.Standby) | ||||
|                     { | ||||
|                         var newDev = (await _collectDeviceService.GetCollectDeviceRuntimeAsync(devId)).FirstOrDefault(); | ||||
|                         if (dev == null) | ||||
|                         { | ||||
|                             _logger.LogError($"更新设备线程失败,不存在{devId}为id的设备"); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             dev.DevicePropertys = newDev.DevicePropertys; | ||||
|                             dev.Redundant = RedundantEnum.Primary; | ||||
|                             _logger?.LogInformation(dev.Name + "切换到主通道"); | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             var Redundantdev = (await _collectDeviceService.GetCollectDeviceRuntimeAsync(dev.RedundantDeviceId)).FirstOrDefault(); | ||||
|                             if (Redundantdev == null) | ||||
|                             { | ||||
|                                 _logger.LogError($"更新设备线程失败,不存在{devId}为id的设备"); | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 dev.DevicePropertys = Redundantdev.DevicePropertys; | ||||
|                                 dev.Redundant = RedundantEnum.Standby; | ||||
|                                 _logger?.LogInformation(dev.Name + "切换到备用通道"); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                         catch | ||||
|                         { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 //初始化 | ||||
|                 devCore.Init(dev); | ||||
|                 //线程管理器移除后,如果不存在其他设备,也删除线程管理器 | ||||
|                 devThread.CollectDeviceCores.Remove(devCore); | ||||
|                 if (devThread.CollectDeviceCores.Count == 0) | ||||
|                 { | ||||
|                     CollectDeviceThreads.Remove(devThread); | ||||
|                 } | ||||
|                 //需判断是否同一通道 | ||||
|                 var newDevThread = DeviceThread(devCore); | ||||
|                 await newDevThread.StartThreadAsync(); | ||||
|  | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 更新设备线程 | ||||
|     /// </summary> | ||||
|     public async Task UpDeviceThreadAsync(long devId, bool isUpdateDb = true) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (singleRestartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             if (!_stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 //如果是组态更改过了,需要重新获取变量/设备运行态的值,其他服务需要先停止 | ||||
|                 if (isUpdateDb) | ||||
|                     await StopOtherHostService(); | ||||
|                 var devThread = CollectDeviceThreads.FirstOrDefault(it => it.CollectDeviceCores.Any(a => a.DeviceId == devId)); | ||||
|                 var devCore = devThread.CollectDeviceCores.FirstOrDefault(a => a.DeviceId == devId); | ||||
|                 if (devThread == null) { throw Oops.Bah($"更新设备线程失败,不存在{devId}为id的设备"); } | ||||
|                 //这里先停止采集,操作会使线程取消,需要重新恢复线程 | ||||
|                 await devThread.StopThreadAsync(); | ||||
|  | ||||
|                 CollectDeviceRunTime dev = isUpdateDb ? (await _collectDeviceService.GetCollectDeviceRuntimeAsync(devId)).FirstOrDefault() : devCore.Device; | ||||
|  | ||||
|                 if (dev == null) | ||||
|                 { | ||||
|                     //线程管理器移除后,如果不存在其他设备,也删除线程管理器 | ||||
|                     devThread.CollectDeviceCores.Remove(devCore); | ||||
|                     if (devThread.CollectDeviceCores.Count == 0) | ||||
|                     { | ||||
|                         CollectDeviceThreads.Remove(devThread); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     //初始化 | ||||
|                     devCore.Init(dev); | ||||
|                     //线程管理器移除后,如果不存在其他设备,也删除线程管理器 | ||||
|                     devThread.CollectDeviceCores.Remove(devCore); | ||||
|  | ||||
|                     if (devThread.CollectDeviceCores.Count == 0) | ||||
|                     { | ||||
|                         CollectDeviceThreads.Remove(devThread); | ||||
|                     } | ||||
|  | ||||
|                     //需判断是否同一通道 | ||||
|                     var newDevThread = DeviceThread(devCore); | ||||
|                     await newDevThread.StartThreadAsync(); | ||||
|  | ||||
|                     //如果是组态更改过了,需要重新获取变量/设备运行态的值 | ||||
|                     if (isUpdateDb) | ||||
|                         await StartOtherHostService(); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region Private | ||||
|     /// <summary> | ||||
|     /// 创建设备采集线程 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private async Task CreatAllDeviceThreadsAsync() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             _logger.LogInformation("正在获取采集组态信息"); | ||||
|             var collectDeviceRunTimes = (await _collectDeviceService.GetCollectDeviceRuntimeAsync()); | ||||
|             _logger.LogInformation("获取采集组态信息完成"); | ||||
|             foreach (var collectDeviceRunTime in collectDeviceRunTimes.Where(a => !collectDeviceRunTimes.Any(b => a.Id == b.RedundantDeviceId && b.IsRedundant))) | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         CollectDeviceCore deviceCollectCore = new(); | ||||
|                         deviceCollectCore.Init(collectDeviceRunTime); | ||||
|                         DeviceThread(deviceCollectCore); | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         _logger.LogError(ex, collectDeviceRunTime.Name); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 根据通道生成/获取线程管理器 | ||||
|     /// </summary> | ||||
|     /// <param name="deviceCollectCore"></param> | ||||
|     /// <returns></returns> | ||||
|     private CollectDeviceThread DeviceThread(CollectDeviceCore deviceCollectCore) | ||||
|     { | ||||
|         if (deviceCollectCore.Driver == null) | ||||
|             return null; | ||||
|         var changelID = deviceCollectCore.Driver.GetChannelID(); | ||||
|         if (changelID.IsSuccess) | ||||
|         { | ||||
|             foreach (var collectDeviceThread in CollectDeviceThreads) | ||||
|             { | ||||
|                 if (collectDeviceThread.ChangelID == changelID.Content) | ||||
|                 { | ||||
|                     collectDeviceThread.CollectDeviceCores.Add(deviceCollectCore); | ||||
|                     return collectDeviceThread; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return NewDeviceThread(deviceCollectCore, changelID.Content); | ||||
|  | ||||
|         CollectDeviceThread NewDeviceThread(CollectDeviceCore deviceCollectCore, string changelID) | ||||
|         { | ||||
|             CollectDeviceThread deviceThread = new(changelID); | ||||
|             deviceThread.CollectDeviceCores.Add(deviceCollectCore); | ||||
|             CollectDeviceThreads.Add(deviceThread); | ||||
|             return deviceThread; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 删除设备线程,并且释放资源 | ||||
|     /// </summary> | ||||
|     private async Task RemoveAllDeviceThreadAsync() | ||||
|     { | ||||
|         await CollectDeviceThreads.ParallelForEachAsync(async (deviceThread, token) => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await deviceThread.BeforeStopThreadAsync(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, deviceThread.ToString()); | ||||
|             } | ||||
|         }, 10); | ||||
|  | ||||
|         await CollectDeviceThreads.ParallelForEachAsync(async (deviceThread, token) => | ||||
|          { | ||||
|              try | ||||
|              { | ||||
|                  await deviceThread.DisposeAsync(); | ||||
|              } | ||||
|              catch (Exception ex) | ||||
|              { | ||||
|                  _logger?.LogError(ex, deviceThread.ToString()); | ||||
|              } | ||||
|          }, 10); | ||||
|         CollectDeviceThreads.Clear(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始设备采集线程 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private async Task StartAllDeviceThreadsAsync() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             foreach (var item in CollectDeviceThreads) | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     await item.StartThreadAsync(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 启动其他后台服务 | ||||
|     /// </summary> | ||||
|     private async Task StartOtherHostService() | ||||
|     { | ||||
|         var alarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>(); | ||||
|         var historyValueService = ServiceHelper.GetBackgroundService<HistoryValueWorker>(); | ||||
|         var uploadDeviceHostService = ServiceHelper.GetBackgroundService<UploadDeviceWorker>(); | ||||
|         var memoryVariableWorker = ServiceHelper.GetBackgroundService<MemoryVariableWorker>(); | ||||
|         await alarmHostService.StartAsync(); | ||||
|         await historyValueService.StartAsync(); | ||||
|         await uploadDeviceHostService.StartAsync(); | ||||
|         await memoryVariableWorker.StartAsync(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 停止其他后台服务 | ||||
|     /// </summary> | ||||
|     private async Task StopOtherHostService() | ||||
|     { | ||||
|         var alarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>(); | ||||
|         var historyValueService = ServiceHelper.GetBackgroundService<HistoryValueWorker>(); | ||||
|         var uploadDeviceHostService = ServiceHelper.GetBackgroundService<UploadDeviceWorker>(); | ||||
|         var memoryVariableWorker = ServiceHelper.GetBackgroundService<MemoryVariableWorker>(); | ||||
|         await alarmHostService.StopAsync(); | ||||
|         await historyValueService.StopAsync(); | ||||
|         await uploadDeviceHostService.StopAsync(); | ||||
|         await memoryVariableWorker.StopAsync(); | ||||
|  | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region 设备信息获取 | ||||
|     /// <summary> | ||||
|     /// GetDebugUI | ||||
|     /// </summary> | ||||
|     /// <param name="driverId"></param> | ||||
|     /// <returns></returns> | ||||
|     public Type GetDebugUI(long driverId) | ||||
|     { | ||||
|         var driverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|         var driverPlugin = driverPluginService.GetDriverPluginById(driverId); | ||||
|         var driver = _pluginService.GetDriver(driverPlugin); | ||||
|         driver?.SafeDispose(); | ||||
|         return driver.DriverDebugUIType; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取设备方法 | ||||
|     /// </summary> | ||||
|     /// <param name="devId"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<string> GetDeviceMethods(long devId) | ||||
|     { | ||||
|         var driverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|         var driverId = _collectDeviceService.GetDeviceById(devId).PluginId; | ||||
|         var driverPlugin = driverPluginService.GetDriverPluginById(driverId); | ||||
|         var driver = (CollectBase)_pluginService.GetDriver(driverPlugin); | ||||
|         var Propertys = _pluginService.GetMethod(driver); | ||||
|         driver?.SafeDispose(); | ||||
|         return Propertys.Select(it => it.Name).ToList(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取设备属性 | ||||
|     /// </summary> | ||||
|     /// <param name="driverId"></param> | ||||
|     /// <param name="devId"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<DependencyProperty> GetDevicePropertys(long driverId, long devId = 0) | ||||
|     { | ||||
|         var driverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|         var driverPlugin = driverPluginService.GetDriverPluginById(driverId); | ||||
|         var driver = _pluginService.GetDriver(driverPlugin); | ||||
|         var Propertys = _pluginService.GetDriverProperties(driver); | ||||
|         if (devId != 0) | ||||
|         { | ||||
|             var devcore = CollectDeviceCores.FirstOrDefault(it => it.Device.Id == devId); | ||||
|             devcore?.Device?.DevicePropertys?.ForEach(it => | ||||
|             { | ||||
|                 var dependencyProperty = Propertys.FirstOrDefault(a => a.PropertyName == it.PropertyName); | ||||
|                 if (dependencyProperty != null) | ||||
|                 { | ||||
|                     dependencyProperty.Value = it.Value; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         driver?.SafeDispose(); | ||||
|         return Propertys; | ||||
|  | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region worker服务 | ||||
|     /// <summary> | ||||
|     /// 在软件关闭时取消 | ||||
|     /// </summary> | ||||
|     private CancellationToken _stoppingToken; | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken token) | ||||
|     { | ||||
|         var hardwareInfoService = ServiceHelper.Services.GetService<HardwareInfoService>(); | ||||
|         hardwareInfoService.Init(); | ||||
|         await base.StartAsync(token); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StopAsync(CancellationToken token) | ||||
|     { | ||||
|         using var stoppingToken = new CancellationTokenSource(); | ||||
|         _stoppingToken = stoppingToken.Token; | ||||
|         stoppingToken.Cancel(); | ||||
|         //停止其他后台服务 | ||||
|         await StopOtherHostService(); | ||||
|         //停止全部采集线程 | ||||
|         await RemoveAllDeviceThreadAsync(); | ||||
|         await base.StopAsync(token); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         //重启采集线程,会启动其他后台服务 | ||||
|         await RestartDeviceThreadAsync(); | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 //检测设备采集线程假死 | ||||
|                 var num = CollectDeviceCores.Count; | ||||
|                 for (int i = 0; i < num; i++) | ||||
|                 { | ||||
|                     CollectDeviceCore devcore = CollectDeviceCores[i]; | ||||
|                     if (devcore.Device != null) | ||||
|                     { | ||||
|                         //超过30分钟,或者(初始化失败并超过10分钟)会重启 | ||||
|                         if ( | ||||
|         (devcore.Device.ActiveTime != DateTime.MinValue && | ||||
|         devcore.Device.ActiveTime.AddMinutes(30) <= SysDateTimeExtensions.CurrentDateTime) | ||||
|         || (devcore.IsInitSuccess == false && devcore.Device.ActiveTime.AddMinutes(10) <= SysDateTimeExtensions.CurrentDateTime) | ||||
|         ) | ||||
|                         { | ||||
|                             //如果线程处于暂停状态,跳过 | ||||
|                             if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause) | ||||
|                                 continue; | ||||
|                             //如果初始化失败 | ||||
|                             if (!devcore.IsInitSuccess) | ||||
|                                 _logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中"); | ||||
|                             else | ||||
|                                 _logger?.LogWarning(devcore.Device.Name + "采集线程假死,重启线程中"); | ||||
|                             //重启线程 | ||||
|                             await UpDeviceThreadAsync(devcore.Device.Id, false); | ||||
|                             break; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             _logger?.LogTrace(devcore.Device.Name + "线程检测正常"); | ||||
|                         } | ||||
|  | ||||
|  | ||||
|                         if (devcore.Device.DeviceStatus == DeviceStatusEnum.OffLine) | ||||
|                         { | ||||
|                             if (devcore.Device.IsRedundant && _collectDeviceService.GetCacheList(false).Any(a => a.Id == devcore.Device.RedundantDeviceId)) | ||||
|                             { | ||||
|                                 await UpDeviceRedundantThreadAsync(devcore.Device.Id); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|                 } | ||||
|                 //每5分钟检测一次 | ||||
|                 await Task.Delay(300000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, ToString()); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,484 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.Foundation.Extension.ConcurrentQueue; | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| using UAParser; | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 实时数据库后台服务 | ||||
| /// </summary> | ||||
| public class HistoryValueWorker : BackgroundService | ||||
| { | ||||
|     private readonly GlobalDeviceData _globalDeviceData; | ||||
|     private readonly ILogger<HistoryValueWorker> _logger; | ||||
|     /// <inheritdoc cref="HistoryValueWorker"/> | ||||
|     public HistoryValueWorker(ILogger<HistoryValueWorker> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         _globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 服务状态 | ||||
|     /// </summary> | ||||
|     public OperResult StatuString { get; set; } = new OperResult("初始化"); | ||||
|  | ||||
|     private ConcurrentQueue<HistoryValue> ChangeDeviceVariables { get; set; } = new(); | ||||
|     private ConcurrentQueue<HistoryValue> DeviceVariables { get; set; } = new(); | ||||
|     /// <summary> | ||||
|     /// 获取数据库链接 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task<OperResult<SqlSugarClient>> GetHisDbAsync() | ||||
|     { | ||||
|         var ConfigService = ServiceHelper.Services.GetService<IConfigService>(); | ||||
|         var hisEnable = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base, ThingsGatewayConfigConst.Config_His_Enable))?.ConfigValue?.ToBoolean(); | ||||
|         var hisDbType = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base, ThingsGatewayConfigConst.Config_His_DbType))?.ConfigValue; | ||||
|         var hisConnstr = (await ConfigService.GetByConfigKeyAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base, ThingsGatewayConfigConst.Config_His_ConnStr))?.ConfigValue; | ||||
|  | ||||
|         if (!(hisEnable == true)) | ||||
|         { | ||||
|             return new OperResult<SqlSugarClient>("历史数据已配置为Disable"); | ||||
|         } | ||||
|  | ||||
|         var configureExternalServices = new ConfigureExternalServices | ||||
|         { | ||||
|             EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required | ||||
|             { | ||||
|                 if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) | ||||
|                     || (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null)) | ||||
|                     column.IsNullable = true; | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
|         DbType type = DbType.QuestDB; | ||||
|         if (!string.IsNullOrEmpty(hisDbType)) | ||||
|         { | ||||
|             if (Enum.TryParse<DbType>(hisDbType, ignoreCase: true, out var result)) | ||||
|             { | ||||
|                 type = result; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return new OperResult<SqlSugarClient>("数据库类型转换失败"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var sqlSugarClient = new SqlSugarClient(new ConnectionConfig() | ||||
|         { | ||||
|             ConnectionString = hisConnstr,//连接字符串 | ||||
|             DbType = type,//数据库类型 | ||||
|             IsAutoCloseConnection = true, //不设成true要手动close | ||||
|             ConfigureExternalServices = configureExternalServices, | ||||
|         }); | ||||
|         return OperResult.CreateSuccessResult(sqlSugarClient); | ||||
|     } | ||||
|  | ||||
|     #region worker服务 | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken token) | ||||
|     { | ||||
|         _logger?.LogInformation("历史服务启动"); | ||||
|         await base.StartAsync(token); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken token) | ||||
|     { | ||||
|         _logger?.LogInformation("历史服务停止"); | ||||
|         return base.StopAsync(token); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await Task.Delay(5000, stoppingToken); | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region core | ||||
|     /// <summary> | ||||
|     /// 循环线程取消标识 | ||||
|     /// </summary> | ||||
|     public ConcurrentList<CancellationTokenSource> StoppingTokens = new(); | ||||
|     /// <summary> | ||||
|     /// 全部重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock restartLock = new(); | ||||
|  | ||||
|     private Task HistoryValueTask; | ||||
|     private bool IsExited; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 离线缓存 | ||||
|     /// </summary> | ||||
|     protected CacheDb CacheDb { get; set; } | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     private async Task InitAsync() | ||||
|     { | ||||
|         CacheDb = new("HistoryValueCache"); | ||||
|         CancellationTokenSource stoppingToken = StoppingTokens.Last(); | ||||
|         HistoryValueTask = await Task.Factory.StartNew(async () => | ||||
|         { | ||||
|             _logger?.LogInformation($"历史数据线程开始"); | ||||
|             await Task.Yield();//返回线程控制,不再阻塞 | ||||
|             try | ||||
|             { | ||||
|                 var result = await GetHisDbAsync(); | ||||
|                 if (!result.IsSuccess) | ||||
|                 { | ||||
|                     _logger?.LogWarning($"历史数据线程即将退出:" + result.Message); | ||||
|                     StatuString = new OperResult($"已退出:{result.Message}"); | ||||
|                     IsExited = true; | ||||
|                     return; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     var sqlSugarClient = result.Content; | ||||
|                     bool LastIsSuccess = true; | ||||
|                     /***创建/更新单个表***/ | ||||
|                     try | ||||
|                     { | ||||
|                         await sqlSugarClient.Queryable<HistoryValue>().FirstAsync(stoppingToken.Token); | ||||
|                         LastIsSuccess = true; | ||||
|                         StatuString = OperResult.CreateSuccessResult(); | ||||
|                     } | ||||
|                     catch (Exception) | ||||
|                     { | ||||
|                         if (stoppingToken.Token.IsCancellationRequested) | ||||
|                         { | ||||
|                             IsExited = true; | ||||
|                             return; | ||||
|                         } | ||||
|                         try | ||||
|                         { | ||||
|                             _logger.LogWarning("连接历史数据表失败,尝试初始化表"); | ||||
|                             sqlSugarClient.CodeFirst.InitTables(typeof(HistoryValue)); | ||||
|                             LastIsSuccess = true; | ||||
|                             StatuString = OperResult.CreateSuccessResult(); | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             LastIsSuccess = false; | ||||
|                             StatuString = new OperResult(ex); | ||||
|                             _logger.LogWarning(ex, "连接历史数据库失败"); | ||||
|                         } | ||||
|                     } | ||||
|                     IsExited = false; | ||||
|  | ||||
|                     while (!stoppingToken.Token.IsCancellationRequested) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             await Task.Delay(500, stoppingToken.Token); | ||||
|  | ||||
|  | ||||
|                             if (stoppingToken.Token.IsCancellationRequested) | ||||
|                                 break; | ||||
|  | ||||
|                             //缓存值 | ||||
|                             var cacheData = await CacheDb.GetCacheData(); | ||||
|                             var data = cacheData.SelectMany(a => a.CacheStr.FromJson<List<HistoryValue>>()).ToList(); | ||||
|                             try | ||||
|                             { | ||||
|                                 var count = await sqlSugarClient.Insertable(data).ExecuteCommandAsync(stoppingToken.Token); | ||||
|                                 await CacheDb.DeleteCacheData(cacheData.Select(a => a.Id).ToArray()); | ||||
|                             } | ||||
|                             catch (Exception ex) | ||||
|                             { | ||||
|                                 if (LastIsSuccess) | ||||
|                                     _logger.LogWarning(ex, "写入历史数据失败"); | ||||
|                             } | ||||
|  | ||||
|                             if (stoppingToken.Token.IsCancellationRequested) | ||||
|                                 break; | ||||
|                             var collectList = DeviceVariables.ToListWithDequeue(); | ||||
|                             if (collectList.Count != 0) | ||||
|                             { | ||||
|                                 ////Sql保存 | ||||
|                                 var collecthis = collectList; | ||||
|                                 int count = 0; | ||||
|                                 //插入 | ||||
|                                 try | ||||
|                                 { | ||||
|                                     count = await sqlSugarClient.Insertable(collecthis).ExecuteCommandAsync(stoppingToken.Token); | ||||
|                                     LastIsSuccess = true; | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     if (LastIsSuccess) | ||||
|                                         _logger.LogWarning(ex, "写入历史数据失败"); | ||||
|                                     var cacheDatas = collecthis.ChunkTrivialBetter(500); | ||||
|                                     foreach (var a in cacheDatas) | ||||
|                                     { | ||||
|                                         await CacheDb.AddCacheData("", a.ToJson(), 50000); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|  | ||||
|                             if (stoppingToken.Token.IsCancellationRequested) | ||||
|                                 break; | ||||
|                             var changeList = ChangeDeviceVariables.ToListWithDequeue(); | ||||
|                             if (changeList.Count != 0) | ||||
|                             { | ||||
|                                 ////Sql保存 | ||||
|                                 var changehis = changeList; | ||||
|                                 int count = 0; | ||||
|                                 //插入 | ||||
|                                 try | ||||
|                                 { | ||||
|                                     count = await sqlSugarClient.Insertable(changehis).ExecuteCommandAsync(stoppingToken.Token); | ||||
|                                     LastIsSuccess = true; | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     if (LastIsSuccess) | ||||
|                                         _logger.LogWarning(ex, "写入历史数据失败"); | ||||
|                                     var cacheDatas = changehis.ChunkTrivialBetter(500); | ||||
|                                     foreach (var a in cacheDatas) | ||||
|                                     { | ||||
|                                         await CacheDb.AddCacheData("", a.ToJson(), 50000); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|  | ||||
|                         } | ||||
|                         catch (TaskCanceledException) | ||||
|                         { | ||||
|  | ||||
|                         } | ||||
|                         catch (ObjectDisposedException) | ||||
|                         { | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             if (LastIsSuccess) | ||||
|                                 _logger?.LogWarning(ex, $"历史数据循环异常"); | ||||
|                             StatuString = new OperResult(ex); | ||||
|                             LastIsSuccess = false; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|  | ||||
|                 IsExited = true; | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|                 IsExited = true; | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 IsExited = true; | ||||
|                 _logger?.LogError(ex, $"历史数据循环异常"); | ||||
|             } | ||||
|             IsExited = true; | ||||
|         } | ||||
|  , TaskCreationOptions.LongRunning); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 重启 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task RestartAsync() | ||||
|     { | ||||
|         await StopAsync(); | ||||
|         await StartAsync(); | ||||
|     } | ||||
|     internal async Task StartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             foreach (var device in _globalDeviceData.CollectDevices) | ||||
|             { | ||||
|                 device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableCollectChange += DeviceVariableCollectChange; }); | ||||
|                 device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableValueChange += DeviceVariableValueChange; }); | ||||
|             } | ||||
|             StoppingTokens.Add(new()); | ||||
|             await InitAsync(); | ||||
|             if (HistoryValueTask.Status == TaskStatus.Created) | ||||
|                 HistoryValueTask.Start(); | ||||
|             IsExited = false; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal async Task StopAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             IsExited = true; | ||||
|  | ||||
|             foreach (var device in _globalDeviceData.CollectDevices) | ||||
|             { | ||||
|                 device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableCollectChange -= DeviceVariableCollectChange; }); | ||||
|                 device.DeviceVariableRunTimes?.Where(a => a.HisEnable == true)?.ForEach(v => { v.VariableValueChange -= DeviceVariableValueChange; }); | ||||
|             } | ||||
|  | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|  | ||||
|             if (HistoryValueTask != null) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     _logger?.LogInformation($"历史数据线程停止中"); | ||||
|                     await HistoryValueTask.WaitAsync(TimeSpan.FromSeconds(10)); | ||||
|                     _logger?.LogInformation($"历史数据线程已停止"); | ||||
|                 } | ||||
|                 catch (ObjectDisposedException) | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                 catch (TimeoutException) | ||||
|                 { | ||||
|                     _logger?.LogWarning($"历史数据线程停止超时,已强制取消"); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger?.LogWarning(ex, "等待历史数据线程停止错误"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             HistoryValueTask?.SafeDispose(); | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.SafeDispose(); | ||||
|             } | ||||
|             StoppingTokens.Clear(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|     private void DeviceVariableCollectChange(DeviceVariableRunTime variable) | ||||
|     { | ||||
|         if (variable.HisType == HisType.Collect && !IsExited) | ||||
|         { | ||||
|             DeviceVariables.Enqueue(variable.Adapt<HistoryValue>()); | ||||
|         } | ||||
|     } | ||||
|     private void DeviceVariableValueChange(DeviceVariableRunTime variable) | ||||
|     { | ||||
|         if (variable.HisType == HisType.Change && !IsExited) | ||||
|         { | ||||
|             ChangeDeviceVariables.Enqueue(variable.Adapt<HistoryValue>()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
| } | ||||
| /// <summary> | ||||
| /// <see cref="HistoryValue"/> Master规则 | ||||
| /// </summary> | ||||
| public class HistoryValueMapper : IRegister | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public void Register(TypeAdapterConfig config) | ||||
|     { | ||||
|         config.ForType<DeviceVariableRunTime, HistoryValue>() | ||||
|             .Map(dest => dest.Value, (src) => ValueReturn(src)) | ||||
|             .Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime());//注意sqlsugar插入时无时区,直接utc时间 | ||||
|     } | ||||
|  | ||||
|     private static object ValueReturn(DeviceVariableRunTime src) | ||||
|     { | ||||
|         if (src.Value?.ToString()?.IsBoolValue() == true) | ||||
|         { | ||||
|             if (src.Value.ToBoolean()) | ||||
|             { | ||||
|                 return 1; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return 0; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return src.Value; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,254 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 实时数据库后台服务 | ||||
| /// </summary> | ||||
| public class MemoryVariableWorker : BackgroundService | ||||
| { | ||||
|     private readonly GlobalDeviceData _globalDeviceData; | ||||
|     private readonly ILogger<MemoryVariableWorker> _logger; | ||||
|     /// <inheritdoc cref="MemoryVariableWorker"/> | ||||
|     public MemoryVariableWorker(ILogger<MemoryVariableWorker> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         _globalDeviceData = ServiceHelper.Services.GetService<GlobalDeviceData>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 服务状态 | ||||
|     /// </summary> | ||||
|     public OperResult StatuString { get; set; } = new OperResult("初始化"); | ||||
|  | ||||
|     #region worker服务 | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken token) | ||||
|     { | ||||
|         _logger?.LogInformation("中间变量服务启动"); | ||||
|         await base.StartAsync(token); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken token) | ||||
|     { | ||||
|         _logger?.LogInformation("中间变量服务停止"); | ||||
|         return base.StopAsync(token); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await Task.Delay(5000, stoppingToken); | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region core | ||||
|     /// <summary> | ||||
|     /// 循环线程取消标识 | ||||
|     /// </summary> | ||||
|     public ConcurrentList<CancellationTokenSource> StoppingTokens = new(); | ||||
|     private Task MemoryWorkerTask; | ||||
|     /// <summary> | ||||
|     /// 全部重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock restartLock = new(); | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     public async Task InitAsync() | ||||
|     { | ||||
|         CancellationTokenSource stoppingToken = StoppingTokens.Last(); | ||||
|         MemoryWorkerTask = await Task.Factory.StartNew(async () => | ||||
|         { | ||||
|             _logger?.LogInformation($"中间变量计算线程开始"); | ||||
|             try | ||||
|             { | ||||
|                 var variableService = ServiceHelper.Services.GetService<IVariableService>(); | ||||
|                 var data = await variableService.GetMemoryVariableRuntimeAsync(); | ||||
|                 _globalDeviceData.MemoryVariables = new(data); | ||||
|                 StatuString = OperResult.CreateSuccessResult(); | ||||
|                 while (!stoppingToken.Token.IsCancellationRequested) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         await Task.Delay(500, stoppingToken.Token); | ||||
|  | ||||
|                         if (stoppingToken.Token.IsCancellationRequested) | ||||
|                             break; | ||||
|                         var isSuccess = true; | ||||
|                         foreach (var item in _globalDeviceData.MemoryVariables) | ||||
|                         { | ||||
|                             if (!string.IsNullOrEmpty(item.ReadExpressions) && item.ProtectTypeEnum != ProtectTypeEnum.WriteOnly) | ||||
|                             { | ||||
|                                 //变量内部已经做了表达式转换,直接赋值0 | ||||
|                                 var operResult = item.SetValue(0); | ||||
|                                 if (!operResult.IsSuccess) | ||||
|                                 { | ||||
|                                     if (StatuString.IsSuccess) | ||||
|                                         _logger?.LogWarning(operResult.Message, ToString()); | ||||
|                                     isSuccess = false; | ||||
|                                     StatuString = operResult; | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|  | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                         if (isSuccess) | ||||
|                             StatuString = OperResult.CreateSuccessResult(); | ||||
|                     } | ||||
|                     catch (TaskCanceledException) | ||||
|                     { | ||||
|  | ||||
|                     } | ||||
|                     catch (ObjectDisposedException) | ||||
|                     { | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         _logger?.LogWarning(ex, $"历史数据循环异常"); | ||||
|                         StatuString = new OperResult(ex); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, $"中间变量计算线程循环异常"); | ||||
|             } | ||||
|         } | ||||
|  , TaskCreationOptions.LongRunning); | ||||
|     } | ||||
|  | ||||
|     internal async Task StartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|  | ||||
|             StoppingTokens.Add(new()); | ||||
|             //初始化线程 | ||||
|             await InitAsync(); | ||||
|             if (MemoryWorkerTask.Status == TaskStatus.Created) | ||||
|                 MemoryWorkerTask.Start(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal async Task StopAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|             if (MemoryWorkerTask != null) | ||||
|             { | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     _logger?.LogInformation($"中间变量计算线程停止中"); | ||||
|                     await MemoryWorkerTask.WaitAsync(TimeSpan.FromSeconds(10)); | ||||
|                     _logger?.LogInformation($"中间变量计算线程已停止"); | ||||
|                 } | ||||
|                 catch (ObjectDisposedException) | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                 catch (TimeoutException) | ||||
|                 { | ||||
|                     _logger?.LogInformation($"中间变量计算线程停止超时,已强制取消"); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger?.LogInformation(ex, "等待线程停止错误"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             MemoryWorkerTask?.SafeDispose(); | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.SafeDispose(); | ||||
|             } | ||||
|             MemoryWorkerTask = null; | ||||
|             StoppingTokens.Clear(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,330 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.FriendlyException; | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备子线程服务 | ||||
| /// </summary> | ||||
| public class UploadDeviceCore | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 全局插件服务 | ||||
|     /// </summary> | ||||
|     private readonly PluginSingletonService _pluginService; | ||||
|     /// <summary> | ||||
|     /// 读写锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock easyLock = new(); | ||||
|     /// <summary> | ||||
|     /// 当前设备信息 | ||||
|     /// </summary> | ||||
|     private UploadDeviceRunTime _device; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前的驱动插件实例 | ||||
|     /// </summary> | ||||
|     private UpLoadBase _driver; | ||||
|     /// <summary> | ||||
|     /// 日志 | ||||
|     /// </summary> | ||||
|     private ILogger _logger; | ||||
|     /// <summary> | ||||
|     /// 是否初始化成功 | ||||
|     /// </summary> | ||||
|     private bool isInitSuccess = true; | ||||
|  | ||||
|     /// <inheritdoc cref="UploadDeviceCore"/> | ||||
|     public UploadDeviceCore() | ||||
|     { | ||||
|         _pluginService = ServiceHelper.Services.GetService<PluginSingletonService>(); | ||||
|         DriverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备 | ||||
|     /// </summary> | ||||
|     public UploadDeviceRunTime Device => _device; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备Id | ||||
|     /// </summary> | ||||
|     public long DeviceId => (long)(_device?.Id.ToLong()); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前插件 | ||||
|     /// </summary> | ||||
|     public UpLoadBase Driver => _driver; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化成功 | ||||
|     /// </summary> | ||||
|     public bool IsInitSuccess => isInitSuccess; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志 | ||||
|     /// </summary> | ||||
|     public ILogger Logger => _logger; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备全部设备属性,执行初始化后获取正确值 | ||||
|     /// </summary> | ||||
|     public List<DependencyProperty> Propertys { get; private set; } | ||||
|  | ||||
|     private IDriverPluginService DriverPluginService { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 暂停上传 | ||||
|     /// </summary> | ||||
|     public void PasueThread(bool keepRun) | ||||
|     { | ||||
|         lock (this) | ||||
|         { | ||||
|             var str = keepRun == false ? "设备线程上传暂停" : "设备线程上传继续"; | ||||
|             _logger?.LogInformation($"{str}:{_device.Name}"); | ||||
|             this.Device.KeepRun = keepRun; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #region 插件处理 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取插件 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private UpLoadBase CreatDriver() | ||||
|     { | ||||
|  | ||||
|         var driverPlugin = DriverPluginService.GetDriverPluginById(_device.PluginId); | ||||
|         if (driverPlugin != null) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 _driver = (UpLoadBase)_pluginService.GetDriver(driverPlugin); | ||||
|                 Propertys = _pluginService.GetDriverProperties(_driver); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 throw Oops.Oh($"创建插件失败:{ex.Message}"); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw Oops.Oh($"找不到驱动{driverPlugin.AssembleName}"); | ||||
|         } | ||||
|         //设置插件配置项 | ||||
|         SetPluginProperties(_device.DevicePropertys); | ||||
|         return _driver; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private void InitDriver() | ||||
|     { | ||||
|         //初始化插件 | ||||
|         _driver.Init(_logger, _device); | ||||
|         //变量打包 | ||||
|         _device.UploadVariableCount = _driver.UploadVariables?.Count ?? 0; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置驱动插件的属性值 | ||||
|     /// </summary> | ||||
|     private void SetPluginProperties(List<DependencyProperty> deviceProperties) | ||||
|     { | ||||
|         if (deviceProperties == null) return; | ||||
|         _pluginService.SetDriverProperties(_driver, deviceProperties); | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 核心读写 | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 线程开始时执行 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     internal async Task BeforeActionAsync(CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (_device == null) | ||||
|             { | ||||
|                 _logger?.LogError(nameof(UploadDeviceRunTime) + "设备不能为null"); | ||||
|                 isInitSuccess = false; | ||||
|                 return; | ||||
|             } | ||||
|             if (_driver == null) | ||||
|             { | ||||
|                 _logger?.LogWarning(_device.Name + " - 插件不能为null"); | ||||
|                 isInitSuccess = false; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             _logger?.LogInformation($"{_device.Name}上传设备线程开始"); | ||||
|  | ||||
|  | ||||
|             InitDriver(); | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (Device.KeepRun == true) | ||||
|                 { | ||||
|                     await _driver.BeforStartAsync(token); | ||||
|                     Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, _device.Name); | ||||
|                 Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message); | ||||
|             } | ||||
|             isInitSuccess = true; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogError(ex, _device.Name); | ||||
|             isInitSuccess = false; | ||||
|             Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999, ex.Message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 结束后 | ||||
|     /// </summary> | ||||
|     internal async Task FinishActionAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _logger?.LogInformation($"{_device.Name}上传线程停止中"); | ||||
|             await _driver?.AfterStopAsync(); | ||||
|             _driver?.SafeDispose(); | ||||
|             _logger?.LogInformation($"{_device.Name}上传线程已停止"); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogError(ex, $"{Device.Name} 释放失败"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isInitSuccess = false; | ||||
|             easyLock.SafeDispose(); | ||||
|         } | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     internal bool Init(UploadDeviceRunTime device) | ||||
|     { | ||||
|         if (device == null) | ||||
|         { | ||||
|             _logger?.LogError(nameof(UploadDeviceRunTime) + "设备不能为null"); | ||||
|             return false; | ||||
|         } | ||||
|         try | ||||
|         { | ||||
|             bool isUpDevice = Device != device; | ||||
|             _device = device; | ||||
|             _logger = ServiceHelper.Services.GetService<ILoggerFactory>().CreateLogger("上传设备:" + _device.Name); | ||||
|             //更新插件信息 | ||||
|             CreatDriver(); | ||||
|             return true; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogError(ex, device.Name); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行一次读取 | ||||
|     /// </summary> | ||||
|     internal async Task<ThreadRunReturn> RunActionAsync(CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (_device == null) | ||||
|             { | ||||
|                 _logger?.LogError(nameof(UploadDeviceRunTime) + "设备不能为null"); | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|             if (_driver == null) | ||||
|             { | ||||
|                 _logger?.LogWarning(_device.Name + " - 插件不能为null"); | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|  | ||||
|             if (Device.KeepRun == false) | ||||
|             { | ||||
|                 //上传暂停 | ||||
|                 return ThreadRunReturn.Continue; | ||||
|             } | ||||
|  | ||||
|             if (token.IsCancellationRequested) | ||||
|                 return ThreadRunReturn.Break; | ||||
|             await _driver.ExecuteAsync(token); | ||||
|  | ||||
|             //获取设备连接状态 | ||||
|             if (_driver.IsConnected()) | ||||
|             { | ||||
|                 //更新设备活动时间 | ||||
|                 Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 0); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Device.SetDeviceStatus(SysDateTimeExtensions.CurrentDateTime, 999); | ||||
|             } | ||||
|             if (token.IsCancellationRequested) | ||||
|                 return ThreadRunReturn.Break; | ||||
|  | ||||
|             //正常返回None | ||||
|             return ThreadRunReturn.None; | ||||
|  | ||||
|         } | ||||
|         catch (TaskCanceledException) | ||||
|         { | ||||
|             return ThreadRunReturn.Break; | ||||
|         } | ||||
|         catch (ObjectDisposedException) | ||||
|         { | ||||
|             return ThreadRunReturn.Break; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger?.LogWarning(ex, $"上传线程循环异常{_device.Name}"); | ||||
|             Device.SetDeviceStatus(null, Device.ErrorCount + 1, ex.Message); | ||||
|             return ThreadRunReturn.None; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,233 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
| /// <summary> | ||||
| /// 上传设备线程管理 | ||||
| /// </summary> | ||||
| public class UploadDeviceThread : IAsyncDisposable | ||||
| { | ||||
|  | ||||
|     /// <summary> | ||||
|     /// CancellationTokenSources | ||||
|     /// </summary> | ||||
|     public ConcurrentList<CancellationTokenSource> StoppingTokens = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 线程 | ||||
|     /// </summary> | ||||
|     protected Task DeviceTask; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 启停锁 | ||||
|     /// </summary> | ||||
|     protected EasyLock easyLock = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认等待间隔时间 | ||||
|     /// </summary> | ||||
|     public static int CycleInterval { get; } = 10; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 上传设备List,在CollectDeviceThread开始前应该初始化内容 | ||||
|     /// </summary> | ||||
|     public ConcurrentList<UploadDeviceCore> UploadDeviceCores { get; private set; } = new(); | ||||
|     /// <inheritdoc/> | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         await StopThreadAsync(); | ||||
|         easyLock.SafeDispose(); | ||||
|         UploadDeviceCores.Clear(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始上传 | ||||
|     /// </summary> | ||||
|     public virtual async Task StartThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             StoppingTokens.Add(new()); | ||||
|             //初始化上传线程 | ||||
|             await InitTaskAsync(); | ||||
|             if (DeviceTask.Status == TaskStatus.Created) | ||||
|                 DeviceTask?.Start(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 停止采集前,提前取消Token | ||||
|     /// </summary> | ||||
|     public virtual async Task BeforeStopThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             if (DeviceTask == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 停止上传 | ||||
|     /// </summary> | ||||
|     public virtual async Task StopThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             if (DeviceTask == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             foreach (var token in StoppingTokens) | ||||
|             { | ||||
|                 token.Cancel(); | ||||
|             } | ||||
|             try | ||||
|             { | ||||
|                 await DeviceTask.WaitAsync(TimeSpan.FromSeconds(10)); | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (TimeoutException) | ||||
|             { | ||||
|                 foreach (var device in UploadDeviceCores) | ||||
|                 { | ||||
|                     device.Logger?.LogInformation($"{device.Device.Name}上传线程停止超时,已强制取消"); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 UploadDeviceCores.FirstOrDefault()?.Logger?.LogError(ex, $"{UploadDeviceCores.FirstOrDefault()?.Device?.Name}上传线程停止错误"); | ||||
|             } | ||||
|             foreach (CancellationTokenSource token in StoppingTokens) | ||||
|             { | ||||
|                 token?.SafeDispose(); | ||||
|             } | ||||
|             DeviceTask?.SafeDispose(); | ||||
|             DeviceTask = null; | ||||
|             StoppingTokens.Clear(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 初始化 | ||||
|     /// </summary> | ||||
|     protected async Task InitTaskAsync() | ||||
|     { | ||||
|         CancellationTokenSource stoppingToken = StoppingTokens.Last(); | ||||
|         DeviceTask = await Task.Factory.StartNew(async () => | ||||
|         { | ||||
|             LoggerGroup log = UploadDeviceCores.FirstOrDefault().Driver.LogMessage; | ||||
|             foreach (var device in UploadDeviceCores) | ||||
|             { | ||||
|                 if (device.Driver == null) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 //添加通道报文到每个设备 | ||||
|                 var data = new EasyLogger(device.Driver.NewMessage) { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|                 log.AddLogger(data); | ||||
|                 await device.BeforeActionAsync(stoppingToken.Token); | ||||
|             } | ||||
|  | ||||
|             while (!stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 foreach (var device in UploadDeviceCores) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         if (stoppingToken.IsCancellationRequested) | ||||
|                             break; | ||||
|                         //初始化成功才能执行 | ||||
|                         if (device.IsInitSuccess) | ||||
|                         { | ||||
|  | ||||
|                             var result = await device.RunActionAsync(stoppingToken.Token); | ||||
|                             if (result == ThreadRunReturn.None) | ||||
|                             { | ||||
|                                 await Task.Delay(CycleInterval); | ||||
|                             } | ||||
|                             else if (result == ThreadRunReturn.Continue) | ||||
|                             { | ||||
|                                 await Task.Delay(1000); | ||||
|                             } | ||||
|                             else if (result == ThreadRunReturn.Break) | ||||
|                             { | ||||
|                                 //当线程返回Break,直接跳出循环 | ||||
|                                 break; | ||||
|                             } | ||||
|  | ||||
|  | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             await Task.Delay(1000); | ||||
|                         } | ||||
|                     } | ||||
|                     catch (TaskCanceledException) | ||||
|                     { | ||||
|  | ||||
|                     } | ||||
|                     catch (ObjectDisposedException) | ||||
|                     { | ||||
|  | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         log.Exception(ex); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             //注意插件结束函数不能使用取消传播作为条件 | ||||
|             foreach (var device in UploadDeviceCores) | ||||
|             { | ||||
|                 //如果插件还没释放,执行一次结束函数 | ||||
|                 if (!device.Driver.DisposedValue) | ||||
|                     await device.FinishActionAsync(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  , TaskCreationOptions.LongRunning); | ||||
|     } | ||||
| } | ||||
| @@ -1,435 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Furion.FriendlyException; | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备上传后台服务 | ||||
| /// </summary> | ||||
| public class UploadDeviceWorker : BackgroundService | ||||
| { | ||||
|     private readonly ILogger<UploadDeviceWorker> _logger; | ||||
|  | ||||
|     private readonly PluginSingletonService _pluginService; | ||||
|  | ||||
|     private readonly IUploadDeviceService _uploadDeviceService; | ||||
|  | ||||
|     /// <inheritdoc cref="UploadDeviceWorker"/> | ||||
|     public UploadDeviceWorker(ILogger<UploadDeviceWorker> logger) | ||||
|     { | ||||
|         _logger = logger; | ||||
|  | ||||
|         _pluginService = ServiceHelper.Services.GetService<PluginSingletonService>(); | ||||
|         _uploadDeviceService = ServiceHelper.Services.GetService<IUploadDeviceService>(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 上传设备List | ||||
|     /// </summary> | ||||
|     public List<UploadDeviceCore> UploadDeviceCores => UploadDeviceThreads | ||||
|         .Where(a => !a.StoppingTokens.Any(b => b.IsCancellationRequested)) | ||||
|         .SelectMany(a => a.UploadDeviceCores).ToList(); | ||||
|     /// <summary> | ||||
|     /// 全部设备子线程 | ||||
|     /// </summary> | ||||
|     private ConcurrentList<UploadDeviceThread> UploadDeviceThreads { get; set; } = new(); | ||||
|  | ||||
|     #region 设备创建更新结束 | ||||
|     /// <summary> | ||||
|     /// 全部重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock restartLock = new(); | ||||
|     /// <summary> | ||||
|     /// 单个重启锁 | ||||
|     /// </summary> | ||||
|     private readonly EasyLock singleRestartLock = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 控制设备线程启停 | ||||
|     /// </summary> | ||||
|     public void ConfigDeviceThread(long deviceId, bool isStart) | ||||
|     { | ||||
|         if (deviceId == 0) | ||||
|             UploadDeviceCores.ForEach(it => it.PasueThread(isStart)); | ||||
|         else | ||||
|             UploadDeviceCores.FirstOrDefault(it => it.DeviceId == deviceId)?.PasueThread(isStart); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 开始 | ||||
|     /// </summary> | ||||
|     public async Task StartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             CreatAllDeviceThreads(); | ||||
|             await StartAllDeviceThreadsAsync(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 停止 | ||||
|     /// </summary> | ||||
|     public async Task StopAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (restartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|  | ||||
|             await RemoveAllDeviceThreadAsync(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 更新设备线程 | ||||
|     /// </summary> | ||||
|     public async Task UpDeviceThreadAsync(long devId) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             //重启操作在未完全之前直接取消 | ||||
|             if (singleRestartLock.IsWaitting) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|  | ||||
|             if (!_stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 var devThread = UploadDeviceThreads.FirstOrDefault(it => it.UploadDeviceCores.Any(a => a.DeviceId == devId)); | ||||
|                 var devCore = devThread.UploadDeviceCores.FirstOrDefault(a => a.DeviceId == devId); | ||||
|                 if (devThread == null) { throw Oops.Bah($"更新设备线程失败,不存在{devId}为id的设备"); } | ||||
|                 //这里先停止上传,操作会使线程取消,需要重新恢复线程 | ||||
|                 await devThread.StopThreadAsync(); | ||||
|                 var dev = _uploadDeviceService.GetUploadDeviceRuntime(devId).FirstOrDefault(); | ||||
|                 if (dev == null) | ||||
|                 { | ||||
|                     //线程管理器移除后,如果不存在其他设备,也删除线程管理器 | ||||
|                     devThread.UploadDeviceCores.Remove(devCore); | ||||
|                     if (devThread.UploadDeviceCores.Count == 0) | ||||
|                     { | ||||
|                         UploadDeviceThreads.Remove(devThread); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     //初始化 | ||||
|                     devCore.Init(dev); | ||||
|  | ||||
|                     //线程管理器移除后,如果不存在其他设备,也删除线程管理器 | ||||
|                     devThread.UploadDeviceCores.Remove(devCore); | ||||
|                     if (devThread.UploadDeviceCores.Count == 0) | ||||
|                     { | ||||
|                         UploadDeviceThreads.Remove(devThread); | ||||
|                     } | ||||
|  | ||||
|                     //需判断是否同一通道 | ||||
|                     var newDevThread = DeviceThread(devCore); | ||||
|                     await newDevThread.StartThreadAsync(); | ||||
|  | ||||
|                 } | ||||
|  | ||||
|  | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 核心 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 创建设备上传线程 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private void CreatAllDeviceThreads() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             _logger.LogInformation("正在获取采集组态信息"); | ||||
|             var collectDeviceRunTimes = (_uploadDeviceService.GetUploadDeviceRuntime()); | ||||
|             _logger.LogInformation("获取采集组态信息完成"); | ||||
|             foreach (var collectDeviceRunTime in collectDeviceRunTimes) | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         UploadDeviceCore deviceCollectCore = new(); | ||||
|                         deviceCollectCore.Init(collectDeviceRunTime); | ||||
|                         DeviceThread(deviceCollectCore); | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         _logger.LogError(ex, collectDeviceRunTime.Name); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private UploadDeviceThread DeviceThread(UploadDeviceCore deviceUploadCore) | ||||
|     { | ||||
|         UploadDeviceThread deviceThread = new(); | ||||
|         deviceThread.UploadDeviceCores.Add(deviceUploadCore); | ||||
|         UploadDeviceThreads.Add(deviceThread); | ||||
|         return deviceThread; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 删除设备线程,并且释放资源 | ||||
|     /// </summary> | ||||
|     private async Task RemoveAllDeviceThreadAsync() | ||||
|     { | ||||
|         await UploadDeviceThreads.ParallelForEachAsync(async (deviceThread, token) => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await deviceThread.BeforeStopThreadAsync(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, deviceThread.ToString()); | ||||
|             } | ||||
|         }, 10); | ||||
|         await UploadDeviceThreads.ParallelForEachAsync(async (deviceThread, token) => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await deviceThread.DisposeAsync(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, deviceThread.ToString()); | ||||
|             } | ||||
|         }, 10); | ||||
|         UploadDeviceThreads.Clear(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始设备上传线程 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private async Task StartAllDeviceThreadsAsync() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             foreach (var item in UploadDeviceThreads) | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     await item.StartThreadAsync(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region 设备信息获取 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取设备属性 | ||||
|     /// </summary> | ||||
|     /// <param name="driverId"></param> | ||||
|     /// <param name="devId"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<DependencyProperty> GetDevicePropertys(long driverId, long devId = 0) | ||||
|     { | ||||
|         var driverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|         var driverPlugin = driverPluginService.GetDriverPluginById(driverId); | ||||
|  | ||||
|         var driver = _pluginService.GetDriver(driverPlugin); | ||||
|         var Propertys = _pluginService.GetDriverProperties(driver); | ||||
|         if (devId != 0) | ||||
|         { | ||||
|             var devcore = UploadDeviceCores.FirstOrDefault(it => it.Device.Id == devId); | ||||
|             devcore?.Device?.DevicePropertys?.ForEach(it => | ||||
|             { | ||||
|                 var dependencyProperty = Propertys.FirstOrDefault(a => a.PropertyName == it.PropertyName); | ||||
|                 if (dependencyProperty != null) | ||||
|                 { | ||||
|                     dependencyProperty.Value = it.Value; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         driver?.SafeDispose(); | ||||
|  | ||||
|         return Propertys; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取变量上传属性 | ||||
|     /// </summary> | ||||
|     /// <param name="driverId"></param> | ||||
|     /// <param name="dependencyProperties"></param> | ||||
|     /// <returns></returns> | ||||
|     public List<DependencyProperty> GetVariablePropertys(long driverId, List<DependencyProperty> dependencyProperties = null) | ||||
|     { | ||||
|         var driverPluginService = ServiceHelper.Services.GetService<IDriverPluginService>(); | ||||
|         var driverPlugin = driverPluginService.GetDriverPluginById(driverId); | ||||
|         var driver = (UpLoadBase)_pluginService.GetDriver(driverPlugin); | ||||
|         var Propertys = _pluginService.GetDriverVariableProperties(driver); | ||||
|         dependencyProperties?.ForEach(it => | ||||
|             { | ||||
|                 var dependencyProperty = Propertys.FirstOrDefault(a => a.PropertyName == it.PropertyName); | ||||
|                 if (dependencyProperty != null) | ||||
|                 { | ||||
|                     dependencyProperty.Value = it.Value; | ||||
|                 } | ||||
|             }); | ||||
|         driver?.SafeDispose(); | ||||
|  | ||||
|         return Propertys; | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region worker服务 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 在软件关闭时取消 | ||||
|     /// </summary> | ||||
|     private CancellationToken _stoppingToken; | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken token) | ||||
|     { | ||||
|         await base.StartAsync(token); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StopAsync(CancellationToken token) | ||||
|     { | ||||
|         using var stoppingToken = new CancellationTokenSource(); | ||||
|         _stoppingToken = stoppingToken.Token; | ||||
|         stoppingToken.Cancel(); | ||||
|         await base.StopAsync(token); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|  | ||||
|  | ||||
|                 //这里不采用CancellationToken控制子线程,直接循环保持,结束时调用子设备线程Dispose | ||||
|                 //检测设备上传线程假死 | ||||
|                 var num = UploadDeviceCores.Count; | ||||
|                 for (int i = 0; i < num; i++) | ||||
|                 { | ||||
|                     UploadDeviceCore devcore = UploadDeviceCores[i]; | ||||
|                     if (devcore.Device != null) | ||||
|                     { | ||||
|                         //超过30分钟,或者(初始化失败并超过10分钟)会重启 | ||||
|                         if ( | ||||
|         (devcore.Device.ActiveTime != DateTime.MinValue | ||||
|         && devcore.Device.ActiveTime.AddMinutes(30) <= SysDateTimeExtensions.CurrentDateTime) | ||||
|         || (devcore.IsInitSuccess == false && devcore.Device.ActiveTime.AddMinutes(10) <= SysDateTimeExtensions.CurrentDateTime) | ||||
|         ) | ||||
|                         { | ||||
|                             //如果线程处于暂停状态,跳过 | ||||
|                             if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause) | ||||
|                                 continue; | ||||
|                             //如果初始化失败 | ||||
|                             if (!devcore.IsInitSuccess) | ||||
|                                 _logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中"); | ||||
|                             else | ||||
|                                 _logger?.LogWarning(devcore.Device.Name + "上传线程假死,重启线程中"); | ||||
|                             //重启线程 | ||||
|  | ||||
|                             await UpDeviceThreadAsync(devcore.Device.Id); | ||||
|                             break; | ||||
|  | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             _logger?.LogTrace(devcore.Device.Name + "线程检测正常"); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|                 //每5分钟检测一次 | ||||
|                 await Task.Delay(300000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|  | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, ToString()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,137 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @using ThingsGateway.Admin.Blazor.Core; | ||||
| @using ThingsGateway.Admin.Core; | ||||
| @using BlazorComponent; | ||||
| @using Microsoft.AspNetCore.Components.Web; | ||||
| @using Microsoft.JSInterop; | ||||
| @using ThingsGateway.Foundation; | ||||
| @using ThingsGateway.Foundation.Extension; | ||||
| @using ThingsGateway.Foundation.Serial; | ||||
| @using Masa.Blazor; | ||||
| @using ThingsGateway.Application; | ||||
| @namespace ThingsGateway.Blazor | ||||
| @inject UserResoures UserResoures; | ||||
| @inherits DriverDebugUIBase | ||||
|  | ||||
| <MCard  Flat Elevation="0"> | ||||
|  | ||||
| @{ | ||||
|     switch (Channel) | ||||
|     { | ||||
|         case ChannelEnum.TcpClientEx: | ||||
|             <TcpClientPage @ref=TcpClientPage></TcpClientPage> | ||||
|             break; | ||||
|         case ChannelEnum.SerialPort: | ||||
|             <SerialClientPage @ref=SerialClientPage></SerialClientPage> | ||||
|             break; | ||||
|         case ChannelEnum.UdpSession: | ||||
|             <UdpSessionPage @ref=UdpSessionPage></UdpSessionPage> | ||||
|             break; | ||||
|         case ChannelEnum.TcpServer: | ||||
|             <TcpServerPage @ref=TcpServerPage></TcpServerPage> | ||||
|             break; | ||||
|     } | ||||
| } | ||||
| @if (Plc != null && ChildContent != null) | ||||
| { | ||||
|     @ChildContent | ||||
| } | ||||
| <MCard Class="pa-4" Flat Elevation="0"> | ||||
|     <MRow Class="my-1" NoGutters> | ||||
|  | ||||
|         <MCol Md="4"> | ||||
|             <MCard Flat Elevation="0"> | ||||
|                 @if (OtherContent!=null) | ||||
|                 { | ||||
|                     @OtherContent | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     <MCol Class="my-1 py-1"> | ||||
|                         <MTooltip Bottom Context="tip"> | ||||
|                             <ActivatorContent> | ||||
|                                 <MTextField Class="mx-1 my-1" Label="变量地址" @attributes="@tip.Attrs" Dense Outlined HideDetails="@("auto")" @bind-Value=@Address /> | ||||
|                             </ActivatorContent> | ||||
|                             <ChildContent> | ||||
|                                 <span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span> | ||||
|                             </ChildContent> | ||||
|                         </MTooltip> | ||||
|                         <MSelect Class="mx-1 my-1" Style="max-width:200px" @bind-Value="DataTypeEnum" Outlined Label="数据类型" | ||||
|                                  Items=@(typeof(DataTypeEnum).GetEnumList()) | ||||
|                                  MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })" | ||||
|                                  ItemText=@((u) =>u.des) | ||||
|                                  ItemValue=@(u =>(DataTypeEnum)u.value) | ||||
|                                  HideDetails=@("auto") Height="30" | ||||
|                               Dense> | ||||
|                     </MSelect> | ||||
|  | ||||
|                     <MButton Class="mx-1 my-1" Color="primary" OnClick="ReadAsync"> | ||||
|                         读取 | ||||
|                     </MButton> | ||||
|                     <MTextarea Class="mx-1 mt-3 my-1" Label="值" Dense Outlined HideDetails="@("auto")" @bind-Value=@WriteValue /> | ||||
|                         <MButton Class="mx-1 my-1" Color="primary" OnClick="WriteAsync"> | ||||
|                             写入 | ||||
|                         </MButton> | ||||
|  | ||||
|                     </MCol> | ||||
|                 } | ||||
|             </MCard> | ||||
|         </MCol> | ||||
|  | ||||
|         <MCol Md="8"> | ||||
|  | ||||
|  | ||||
|             <MCard Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+300}px)") Style="overflow-y:auto;width:100%" Elevation="0" Flat Class="ml-4"> | ||||
|                 <MCardActions> | ||||
|                     输出日志 | ||||
|                     <MSpacer></MSpacer> | ||||
|  | ||||
|                     <MTooltip Bottom Context="tip"> | ||||
|                         <ActivatorContent> | ||||
|                             <MButton Loading=isDownExport Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small | ||||
|                                      OnClick=@(async()=> | ||||
|                                      { | ||||
|                                      await DownDeviceMessageExportAsync(Messages.Select(a=>a.message)); | ||||
|                                      } | ||||
|                                      )> | ||||
|                                 <MIcon>mdi-export</MIcon> | ||||
|                             </MButton> | ||||
|                         </ActivatorContent> | ||||
|                         <ChildContent> | ||||
|                             <span>导出</span> | ||||
|                         </ChildContent> | ||||
|                     </MTooltip> | ||||
|  | ||||
|                 </MCardActions> | ||||
|                 @{ | ||||
|                     var item = Messages; | ||||
|                     <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                         <MVirtualScroll Context="itemMessage" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+400}px)") OverscanCount=2 ItemSize="100" Items="item"> | ||||
|                             <ItemContent> | ||||
|                                 <div title=@itemMessage.message class=@(itemMessage.level<Microsoft.Extensions.Logging.LogLevel.Information?UserResoures.IsDark? " while--text ":"black--text":itemMessage.level>=Microsoft.Extensions.Logging.LogLevel.Warning?" red--text ":"green--text ") style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;"> | ||||
|                                     @itemMessage.message | ||||
|                                 </div> | ||||
|                             </ItemContent> | ||||
|                         </MVirtualScroll> | ||||
|                     </MRow> | ||||
|                 } | ||||
|             </MCard> | ||||
|         </MCol> | ||||
|  | ||||
|     </MRow> | ||||
|  | ||||
| </MCard> | ||||
|  | ||||
| </MCard> | ||||
|  | ||||
| @@ -1,109 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using ThingsGateway.Application; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <summary> | ||||
| /// <inheritdoc/> | ||||
| /// </summary> | ||||
| public enum ChannelEnum | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     None = 0, | ||||
|     /// <inheritdoc/> | ||||
|     TcpClientEx = 1, | ||||
|     /// <inheritdoc/> | ||||
|     SerialPort = 2, | ||||
|     /// <inheritdoc/> | ||||
|     UdpSession = 3, | ||||
|     /// <inheritdoc/> | ||||
|     TcpServer = 4, | ||||
| } | ||||
| /// <inheritdoc/> | ||||
| public partial class DefalutDebugDriverPage : DriverDebugUIBase | ||||
| { | ||||
|     /// <summary> | ||||
|     /// SerialClientPage | ||||
|     /// </summary> | ||||
|     public SerialClientPage SerialClientPage; | ||||
|     /// <summary> | ||||
|     /// TcpClientPage | ||||
|     /// </summary> | ||||
|     public TcpClientPage TcpClientPage; | ||||
|     /// <summary> | ||||
|     /// TcpServerPage | ||||
|     /// </summary> | ||||
|     public TcpServerPage TcpServerPage; | ||||
|     /// <summary> | ||||
|     /// UdpSessionPage | ||||
|     /// </summary> | ||||
|     public UdpSessionPage UdpSessionPage; | ||||
|     /// <summary> | ||||
|     /// ѡ<><D1A1><EFBFBD><EFBFBD>1-TCPCLIENT<4E><54>2-<2D><><EFBFBD>ڣ<EFBFBD>3-UDP<44><50>4-TCPServer | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public ChannelEnum Channel { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     public override ThingsGateway.Foundation.IReadWriteDevice Plc { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// ģ<><C4A3> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public RenderFragment ChildContent { get; set; } | ||||
|     /// <summary> | ||||
|     /// <20>Զ<EFBFBD><D4B6><EFBFBD>ģ<EFBFBD><C4A3> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public RenderFragment OtherContent { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     public override void Dispose() | ||||
|     { | ||||
|         Plc?.SafeDispose(); | ||||
|         TcpClientPage?.SafeDispose(); | ||||
|         SerialClientPage?.SafeDispose(); | ||||
|         TcpServerPage?.SafeDispose(); | ||||
|         UdpSessionPage?.SafeDispose(); | ||||
|         base.Dispose(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     /// <param name="firstRender"></param> | ||||
|     protected override void OnAfterRender(bool firstRender) | ||||
|     { | ||||
|         if (firstRender) | ||||
|         { | ||||
|             if (TcpClientPage != null) | ||||
|                 TcpClientPage.LogAction = LogOut; | ||||
|             if (SerialClientPage != null) | ||||
|                 SerialClientPage.LogAction = LogOut; | ||||
|             if (TcpServerPage != null) | ||||
|                 TcpServerPage.LogAction = LogOut; | ||||
|             if (UdpSessionPage != null) | ||||
|                 UdpSessionPage.LogAction = LogOut; | ||||
|             //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
|             StateHasChanged(); | ||||
|         } | ||||
|  | ||||
|         base.OnAfterRender(firstRender); | ||||
|     } | ||||
| } | ||||
| @@ -1,259 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Microsoft.JSInterop; | ||||
|  | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
|  | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
| using ThingsGateway.Application.Extensions; | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
|  | ||||
| namespace ThingsGateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 调试UI | ||||
| /// </summary> | ||||
| public abstract class DriverDebugUIBase : ComponentBase, IDisposable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 导出提示 | ||||
|     /// </summary> | ||||
|     public bool isDownExport; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 日志缓存 | ||||
|     /// </summary> | ||||
|     public ConcurrentLinkedList<(LogLevel level, string message)> Messages = new(); | ||||
|  | ||||
|     IJSObjectReference _helper; | ||||
|     readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(1)); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认读写设备 | ||||
|     /// </summary> | ||||
|     public virtual IReadWriteDevice Plc { get; set; } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 变量地址 | ||||
|     /// </summary> | ||||
|     public virtual string Address { get; set; } = "40001"; | ||||
|     /// <summary> | ||||
|     /// 数据类型 | ||||
|     /// </summary> | ||||
|     protected virtual DataTypeEnum DataTypeEnum { get; set; } = DataTypeEnum.Int16; | ||||
|     /// <inheritdoc/> | ||||
|     [Inject] | ||||
|     protected IJSRuntime JS { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 写入值 | ||||
|     /// </summary> | ||||
|     public virtual string WriteValue { get; set; } | ||||
|     [Inject] | ||||
|     private ICollectDeviceService CollectDeviceService { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     private IVariableService VariableService { get; set; } | ||||
|     [Inject] | ||||
|     private InitTimezone InitTimezone { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual void Dispose() | ||||
|     { | ||||
|         _periodicTimer?.Dispose(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual async Task ReadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var data = await Plc.ReadAsync(Address, DataTypeEnum.GetSystemType()); | ||||
|             if (data.IsSuccess) | ||||
|             { | ||||
|                 Messages.Add((LogLevel.Information, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - 对应类型值:" + data.Content)); | ||||
|  | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Messages.Add((LogLevel.Error, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + data.Message)); | ||||
|  | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Messages.Add((LogLevel.Warning, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + "错误:" + ex.Message)); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual async Task WriteAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var data = await Plc.WriteAsync(Address, DataTypeEnum.GetSystemType(), WriteValue); | ||||
|             if (data.IsSuccess) | ||||
|             { | ||||
|                 Messages.Add((LogLevel.Information, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + data.Message)); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Messages.Add((LogLevel.Warning, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + data.Message)); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Messages.Add((LogLevel.Error, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + "写入前失败:" + ex.Message)); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 导入设备 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task DeviceImportAsync(CollectDevice data) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             isDownExport = true; | ||||
|             StateHasChanged(); | ||||
|             await CollectDeviceService.AddAsync(data); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isDownExport = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导入变量 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task DeviceVariableImportAsync(List<DeviceVariable> data) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             isDownExport = true; | ||||
|             StateHasChanged(); | ||||
|             await VariableService.AddBatchAsync(data); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isDownExport = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导出 | ||||
|     /// </summary> | ||||
|     /// <param name="values"></param> | ||||
|     /// <returns></returns> | ||||
|     public async Task DownDeviceMessageExportAsync(IEnumerable<string> values) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             isDownExport = true; | ||||
|             StateHasChanged(); | ||||
|             using var memoryStream = new MemoryStream(); | ||||
|             StreamWriter writer = new(memoryStream); | ||||
|             foreach (var item in values) | ||||
|             { | ||||
|                 writer.WriteLine(item); | ||||
|             } | ||||
|             writer.Flush(); | ||||
|             memoryStream.Seek(0, SeekOrigin.Begin); | ||||
|             using var streamRef = new DotNetStreamReference(stream: memoryStream); | ||||
|             _helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js"); | ||||
|             await _helper.InvokeVoidAsync("downloadFileFromStream", $"报文导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.txt", streamRef); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isDownExport = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导出到excel | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task DownDeviceExportAsync(CollectDevice data) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             isDownExport = true; | ||||
|             StateHasChanged(); | ||||
|             using var memoryStream = await CollectDeviceService.ExportFileAsync(new List<CollectDevice>() { data }); | ||||
|             memoryStream.Seek(0, SeekOrigin.Begin); | ||||
|             using var streamRef = new DotNetStreamReference(stream: memoryStream); | ||||
|             _helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js"); | ||||
|             await _helper.InvokeVoidAsync("downloadFileFromStream", $"设备导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isDownExport = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导出到excel | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public async Task DownDeviceVariableExportAsync(List<DeviceVariable> data, string devName) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             isDownExport = true; | ||||
|             StateHasChanged(); | ||||
|             using var memoryStream = await VariableService.ExportFileAsync(data, devName); | ||||
|             memoryStream.Seek(0, SeekOrigin.Begin); | ||||
|             using var streamRef = new DotNetStreamReference(stream: memoryStream); | ||||
|             _helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js"); | ||||
|             await _helper.InvokeVoidAsync("downloadFileFromStream", $"变量导出{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.xlsx", streamRef); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isDownExport = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void LogOut(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception) | ||||
|     { | ||||
|         Messages.Add(((LogLevel)logLevel, SysDateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + message + (exception != null ? exception.Message : ""))); | ||||
|         if (Messages.Count > 2500) | ||||
|         { | ||||
|             Messages.Clear(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         _ = RunTimerAsync(); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|     private async Task RunTimerAsync() | ||||
|     { | ||||
|         while (await _periodicTimer.WaitForNextTickAsync()) | ||||
|         { | ||||
|             await InvokeAsync(StateHasChanged); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,92 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using ThingsGateway.Foundation.Serial; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public partial class SerialClientPage | ||||
| { | ||||
|     /// <summary> | ||||
|     /// <20><>־<EFBFBD><D6BE><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     public Action<LogLevel, object, string, Exception> LogAction; | ||||
|  | ||||
|     private TouchSocketConfig config; | ||||
|  | ||||
|     private readonly SerialProperty serialProperty = new(); | ||||
|  | ||||
|     private SerialClient SerialClient { get; set; } = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Dispose() | ||||
|     { | ||||
|         SerialClient.SafeDispose(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public SerialClient GetSerialClient() | ||||
|     { | ||||
|         config?.Dispose(); | ||||
|         config = new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetSerialProperty(serialProperty); | ||||
|         //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
|         SerialClient.Setup(config); | ||||
|         return SerialClient; | ||||
|     } | ||||
|     private async Task ConnectAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SerialClient.Close(); | ||||
|             await GetSerialClient().ConnectAsync(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     private void DisConnect() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SerialClient.Close(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         config ??= new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         SerialClient = new SerialClient(); | ||||
|         SerialClient.Setup(config); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); | ||||
|  | ||||
| } | ||||
| @@ -1,107 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public partial class TcpClientPage | ||||
| { | ||||
|     /// <summary> | ||||
|     /// <20><>־<EFBFBD><D6BE><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     public Action<LogLevel, object, string, Exception> LogAction; | ||||
|  | ||||
|     private TouchSocketConfig config; | ||||
|     /// <summary> | ||||
|     /// IP | ||||
|     /// </summary> | ||||
|     private string IP = "127.0.0.1"; | ||||
|     /// <summary> | ||||
|     /// <20>˿<EFBFBD> | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public int Port { get; set; } = 502; | ||||
|  | ||||
|     private TcpClientEx TcpClientEx { get; set; } = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Dispose() | ||||
|     { | ||||
|         TcpClientEx.SafeDispose(); | ||||
|     } | ||||
|  | ||||
|     private async Task ConnectAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             TcpClientEx.Close(); | ||||
|             await GetTcpClient().ConnectAsync(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|  | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     private void DisConnect() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             TcpClientEx.Close(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|  | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public TcpClientEx GetTcpClient() | ||||
|     { | ||||
|         config ??= new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetRemoteIPHost(new IPHost(IP + ":" + Port)).SetBufferLength(300); | ||||
|         //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
|         TcpClientEx.Setup(config); | ||||
|         return TcpClientEx; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         config?.Dispose(); | ||||
|         config = new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetRemoteIPHost(new IPHost(IP + ":" + Port)).SetBufferLength(300); | ||||
|         TcpClientEx = new TcpClientEx(); | ||||
|         TcpClientEx.Setup(config); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); | ||||
|  | ||||
| } | ||||
| @@ -1,99 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public partial class TcpServerPage | ||||
| { | ||||
|     /// <summary> | ||||
|     /// <20><>־<EFBFBD><D6BE><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     public Action<LogLevel, object, string, Exception> LogAction; | ||||
|  | ||||
|     private TouchSocketConfig config; | ||||
|  | ||||
|     private string ip = "127.0.0.1"; | ||||
|  | ||||
|     private int port = 502; | ||||
|  | ||||
|     private TcpService TcpServer { get; set; } = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Dispose() | ||||
|     { | ||||
|         TcpServer.SafeDispose(); | ||||
|     } | ||||
|     private void Connect() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             TcpServer.Stop(); | ||||
|             GetTcpServer().Start(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|  | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     private void DisConnect() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             TcpServer.Stop(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public TcpService GetTcpServer() | ||||
|     { | ||||
|         config ??= new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) }); | ||||
|         config.SetBufferLength(300); | ||||
|         //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
|         TcpServer.Setup(config); | ||||
|         return TcpServer; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         config?.Dispose(); | ||||
|         config = new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) }); | ||||
|         config.SetBufferLength(300); | ||||
|         TcpServer = new TcpService(); | ||||
|         TcpServer.Setup(config); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); | ||||
|  | ||||
| } | ||||
| @@ -1,99 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public partial class UdpSessionPage : IDisposable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// <20><>־<EFBFBD><D6BE><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     public Action<LogLevel, object, string, Exception> LogAction; | ||||
|  | ||||
|     private TouchSocketConfig config; | ||||
|  | ||||
|     private string ip = "127.0.0.1"; | ||||
|  | ||||
|     private int port = 502; | ||||
|  | ||||
|     private UdpSession UdpSession { get; set; } = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void Dispose() | ||||
|     { | ||||
|         UdpSession.SafeDispose(); | ||||
|     } | ||||
|     private void Connect() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             UdpSession.Stop(); | ||||
|             GetUdpSession().Start(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     private void DisConnect() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             UdpSession.Stop(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|  | ||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public UdpSession GetUdpSession() | ||||
|     { | ||||
|         config ??= new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetRemoteIPHost(new IPHost(ip + ":" + port)).SetBufferLength(300); | ||||
|         config.SetBindIPHost(new IPHost(0)); | ||||
|         //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||
|         UdpSession.Setup(config); | ||||
|         return UdpSession; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         config?.Dispose(); | ||||
|         config = new TouchSocketConfig(); | ||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; | ||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); | ||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||
|         config.SetRemoteIPHost(new IPHost(ip + ":" + port)).SetBufferLength(300); | ||||
|         config.SetBindIPHost(new IPHost(0)); | ||||
|         UdpSession = new UdpSession(); | ||||
|         UdpSession.Setup(config); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/gatewaylog/backendlog" | ||||
| @using System.Linq.Expressions; | ||||
| @inject IBackendLogService BackendLogService | ||||
| @inject UserResoures UserResoures | ||||
| @namespace ThingsGateway.Blazor | ||||
| @using BlazorComponent; | ||||
| @using Masa.Blazor | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using ThingsGateway.Admin.Blazor.Core; | ||||
| @using ThingsGateway.Admin.Blazor; | ||||
| @using ThingsGateway.Admin.Core; | ||||
| @using ThingsGateway.Application; | ||||
| @attribute [Authorize] | ||||
| @inherits BaseComponentBase | ||||
| @layout MainLayout | ||||
| <AppDataTable @ref="_datatable" TItem="BackendLog" SearchItem="BackendLogPageInput" | ||||
|               AddItem="object" EditItem="object" IsShowSelect=false | ||||
|               SearchModel="@search" IsShowOperCol=true | ||||
|               QueryCallAsync="QueryCallAsync" | ||||
|                 IsShowDetailButton | ||||
|                 IsShowQueryButton> | ||||
|     <SearchTemplate> | ||||
|         <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.Source" | ||||
|                         Outlined Label=@context.Description(x => x.Source) /> | ||||
|         <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.Level" | ||||
|                         Outlined Label=@context.Description(x => x.Level) /> | ||||
|  | ||||
|     </SearchTemplate> | ||||
|     <OtherToolbarTemplate> | ||||
|         @if (@UserResoures.IsHasButtonWithRole("gatewaybackendlogclear")) | ||||
|         { | ||||
|             <MButton Color="error" Class="mx-2 my-1" OnClick=@(ClearClickAsync)>清空</MButton> | ||||
|         } | ||||
|         <MMenu OffsetY Context="menu"> | ||||
|             <ActivatorContent> | ||||
|                 <MButton @attributes="@menu.Attrs" Color="primary" | ||||
|                          Class="my-1  mx-2 "> | ||||
|                     导出 | ||||
|                     <AppChevronDown></AppChevronDown> | ||||
|                 </MButton> | ||||
|             </ActivatorContent> | ||||
|             <ChildContent> | ||||
|                 <MList> | ||||
|                     <MListItem OnClick="()=>DownExportAsync()"> 导出全部</MListItem> | ||||
|                     <MListItem OnClick="()=>DownExportAsync(search)">  导出搜索项 </MListItem> | ||||
|                 </MList> | ||||
|  | ||||
|             </ChildContent> | ||||
|         </MMenu> | ||||
|  | ||||
|     </OtherToolbarTemplate> | ||||
| </AppDataTable> | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,303 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/gatewayconfig/collectdevice" | ||||
| @namespace ThingsGateway.Blazor | ||||
| @using System.Linq.Expressions; | ||||
| @using BlazorComponent; | ||||
| @using Mapster; | ||||
| @using Masa.Blazor | ||||
| @using Masa.Blazor.Presets; | ||||
| @using System.IO; | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using ThingsGateway.Admin.Blazor.Core; | ||||
| @using ThingsGateway.Admin.Blazor; | ||||
| @using ThingsGateway.Application; | ||||
| @inject ICollectDeviceService CollectDeviceService | ||||
| @attribute [Authorize] | ||||
| @inherits BaseComponentBase | ||||
| @inject UserResoures UserResoures | ||||
| @inject IDriverPluginService DriverPluginService | ||||
| @layout MainLayout | ||||
| @using ThingsGateway.Admin.Core; | ||||
| @if (IsMobile) | ||||
| { | ||||
|     @GetAppDataTable() | ||||
| } | ||||
| else | ||||
| { | ||||
|     <MRow> | ||||
|         <MCol Md=2 Cols="12"> | ||||
|             <MCard Class="ma-2" Height=@("100%")> | ||||
|                 <MCardTitle> | ||||
|                     <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_searchName" | ||||
|                                 Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) /> | ||||
|                 </MCardTitle> | ||||
|                 <MTreeview  | ||||
|                     Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto")  | ||||
|                 Dense TItem="string" TKey="string" ActiveChanged=@(async a=> | ||||
|                        { | ||||
|                        if(search.DeviceGroup!=a.FirstOrDefault()) | ||||
|                        { | ||||
|                        search.DeviceGroup=a.FirstOrDefault(); | ||||
|                        await DatatableQueryAsync(); | ||||
|                        } | ||||
|                        } ) | ||||
|                            Items="_deviceGroups" ItemText="r=>r" ItemChildren="r=>null" | ||||
|                            Search="@_searchName" | ||||
|                           Activatable ItemKey=@(r=>r)> | ||||
|                     <LabelContent> | ||||
|                         <span title=@context.Item> | ||||
|                             @context.Item | ||||
|                         </span> | ||||
|                     </LabelContent> | ||||
|                 </MTreeview> | ||||
|             </MCard> | ||||
|         </MCol> | ||||
|         <MCol Md=10 Cols="12"> | ||||
|  | ||||
|             @GetAppDataTable() | ||||
|         </MCol> | ||||
|     </MRow> | ||||
| } | ||||
|  | ||||
|  | ||||
| <ImportExcel @ref=ImportExcel Import="SaveDeviceImportAsync" Preview="DeviceImportAsync" /> | ||||
|  | ||||
|  | ||||
| @code { | ||||
|     RenderFragment GetAppDataTable() | ||||
|     { | ||||
|         RenderFragment renderFragment = | ||||
|     @<AppDataTable @ref="_datatable" | ||||
|                        StyleString=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+10}px)") | ||||
|                        TItem="CollectDevice" SearchItem="CollectDevicePageInput" | ||||
|                        AddItem="CollectDeviceAddInput" EditItem="CollectDeviceEditInput" | ||||
|                        IsMenuOperTemplate=false SearchModel="@search" | ||||
|                        QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync" | ||||
|                        EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync" | ||||
|                                   IsShowDetailButton | ||||
|                                   IsShowQueryButton | ||||
|                        IsShowAddButton=@UserResoures.IsHasButtonWithRole("gatewaycollectdeviceadd") | ||||
|                        IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("gatewaycollectdevicedelete") | ||||
|                        IsShowEditButton=@UserResoures.IsHasButtonWithRole("gatewaycollectdeviceedit")> | ||||
|         <AddTemplate> | ||||
|             @GetRenderFragment(context) | ||||
|         </AddTemplate> | ||||
|  | ||||
|         <EditTemplate> | ||||
|             @GetRenderFragment(context) | ||||
|  | ||||
|         </EditTemplate> | ||||
|  | ||||
|         <SearchTemplate> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.Name" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.Name) /> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.PluginName" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.PluginName) /> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.DeviceGroup" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.DeviceGroup) /> | ||||
|         </SearchTemplate> | ||||
|         <OtherToolbarTemplate> | ||||
|  | ||||
|             <MMenu OffsetY | ||||
|                    Context="menu"> | ||||
|                 <ActivatorContent> | ||||
|                     <MButton @attributes="@menu.Attrs" Color="primary" | ||||
|                              Class="my-1  mx-2 "> | ||||
|                         复制 | ||||
|                         <AppChevronDown></AppChevronDown> | ||||
|                     </MButton> | ||||
|                 </ActivatorContent> | ||||
|                 <ChildContent> | ||||
|                     <MList> | ||||
|                         <MListItem OnClick="()=>CopyDeviceAsync(context)">复制设备</MListItem> | ||||
|                         <MListItem OnClick="()=>CopyDevAndVarAsync(context)">复制设备及设备下变量</MListItem> | ||||
|                     </MList> | ||||
|  | ||||
|                 </ChildContent> | ||||
|             </MMenu> | ||||
|  | ||||
|             <MMenu OffsetY | ||||
|                    Context="menu"> | ||||
|                 <ActivatorContent> | ||||
|                     <MButton @attributes="@menu.Attrs" Color="primary" | ||||
|                              Class="my-1  mx-2 "> | ||||
|                         导出 | ||||
|                         <AppChevronDown></AppChevronDown> | ||||
|                     </MButton> | ||||
|                 </ActivatorContent> | ||||
|                 <ChildContent> | ||||
|                     <MList> | ||||
|                         <MListItem OnClick="()=>DownExportAsync()">导出全部</MListItem> | ||||
|                         <MListItem OnClick="()=>DownExportAsync(search)">导出搜索项</MListItem> | ||||
|                     </MList> | ||||
|  | ||||
|                 </ChildContent> | ||||
|             </MMenu> | ||||
|  | ||||
|             <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaycollectdeviceedit")) Class="my-1  mx-2" OnClick="()=>{ ImportExcel.Step=1;  ImportExcel.IsShowImport=true;}" Color="primary"> | ||||
|                 导入 | ||||
|             </MButton> | ||||
|         </OtherToolbarTemplate> | ||||
|  | ||||
|  | ||||
|  | ||||
|         <ItemColTemplate> | ||||
|             @switch (context.Header.Value) | ||||
|             { | ||||
|  | ||||
|                 case nameof(context.Item.Enable): | ||||
|                     <EnableChip Value="context.Item.Enable"> | ||||
|                     </EnableChip> | ||||
|                     break; | ||||
|                 case nameof(context.Item.PluginId): | ||||
|                         <span title=@context.Value> | ||||
|                         @( | ||||
|                             DriverPluginService.GetNameById(context.Item.PluginId) | ||||
|                                 ) | ||||
|                     </span> | ||||
|                     break; | ||||
|                 default: | ||||
|                     @if (context.Header.CellClass?.Contains("text-truncate") == true) | ||||
|                     { | ||||
|                         <span title=@context.Value> | ||||
|                             @context.Value | ||||
|                         </span> | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         @context.Value | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|             } | ||||
|  | ||||
|         </ItemColTemplate> | ||||
|  | ||||
|  | ||||
|     </AppDataTable> | ||||
|     ; | ||||
|         return renderFragment; | ||||
|     } | ||||
| } | ||||
| @code { | ||||
|  | ||||
|     RenderFragment GetRenderFragment(CollectDeviceAddInput context) | ||||
|     { | ||||
|         RenderFragment renderFragment = | ||||
|     @<div> | ||||
|         <MTabs @bind-Value="tab"> | ||||
|             <MTab Value="1" Style="height:50px;">   基本信息    </MTab> | ||||
|             <MTab Value="2"> 扩展属性</MTab> | ||||
|         </MTabs> | ||||
|  | ||||
|         <MTabsItems Value="tab"> | ||||
|             <MTabItem Value="1"> | ||||
|                 @if (tab == 1) | ||||
|                 { | ||||
|                     <MCard Flat Class="ma-2"> | ||||
|                             <MSubheader Class="mt-4 font-weight-black"> @(context.Description(x => x.Name))  </MSubheader> | ||||
|                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name /> | ||||
|  | ||||
|                             <MSubheader Class="mt-4 font-weight-black">    @(context.Description(x => x.Description))  </MSubheader> | ||||
|                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Description /> | ||||
|  | ||||
|  | ||||
|                             <MSubheader Class="mt-4 font-weight-black">    @(context.Description(x => x.DeviceGroup))  </MSubheader> | ||||
|                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.DeviceGroup /> | ||||
|  | ||||
|                             <MSubheader Class="mt-4 font-weight-black">    @(typeof(CollectDeviceRunTime).GetDescription(nameof(CollectDeviceRunTime.PluginName)))  </MSubheader> | ||||
|                         <MCascader Value="context.PluginId" Class="mt-3 mr-3" Clearable TValue=long TItem="DriverPluginCategory" | ||||
|                                        ValueChanged=@(async a=>await DriverValueChangedAsync(context,a)) | ||||
|                                        Items="DriverPlugins" ItemText="u => u.Name" ItemValue="u => u.Id" ItemChildren="u => u.Children" | ||||
|                                        MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })" | ||||
|                                        ShowAllLevels="false" HideDetails="@("auto")" Height="30" Dense> | ||||
|                         </MCascader> | ||||
|  | ||||
|                         <MSubheader Class="mt-4 font-weight-black">    @(context.Description(x => x.Enable))  </MSubheader> | ||||
|                         <MSwitch @bind-Value=@context.Enable /> | ||||
|  | ||||
|  | ||||
|                         <MSubheader Class="mt-4 font-weight-black">    @(context.Description(x => x.IsLogOut))  </MSubheader> | ||||
|                         <MSwitch @bind-Value=@context.IsLogOut /> | ||||
|  | ||||
|  | ||||
|                         <MSubheader Class="mt-4 font-weight-black">    @(context.Description(x => x.IsRedundant))  </MSubheader> | ||||
|                         <MSwitch @bind-Value=@context.IsRedundant /> | ||||
|  | ||||
|                         <MSubheader Class="font-weight-black">    @context.Description(x=>x.RedundantDeviceId)  </MSubheader> | ||||
|                         <MSelect Class="mr-3" @bind-Value=@context.RedundantDeviceId Outlined | ||||
|                                      Items=@(CollectDevices) | ||||
|                                      MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                      ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id) | ||||
|                                      ItemDisabled="u => (u.PluginId!=context.PluginId||u.Id==context.Id)" | ||||
|                                      HideDetails=@("auto") Height="30" Dense> | ||||
|                         </MSelect> | ||||
|  | ||||
|  | ||||
|                     </MCard> | ||||
|                 } | ||||
|             </MTabItem> | ||||
|             <MTabItem Value="2"> | ||||
|                 @if (tab == 2) | ||||
|                 { | ||||
|                     <MCard Flat Class="ma-2"> | ||||
|                             <MButton Class="my-3" OnClick=@(async() => | ||||
|                          { | ||||
|                          if(context.PluginId>0) | ||||
|                          { | ||||
|                          context.DevicePropertys= GetDriverProperties(context.PluginId,context.Id); | ||||
|                          } | ||||
|                          else | ||||
|                          { | ||||
|                          await PopupService.EnqueueSnackbarAsync("需选择驱动",AlertTypes.Error); | ||||
|                          } | ||||
|                          } | ||||
|                          ) Color="primary"> | ||||
|                                 刷新设备属性 | ||||
|                             </MButton> | ||||
|                             @if (context.DevicePropertys != null) | ||||
|                         { | ||||
|                             @foreach (var item in context.DevicePropertys) | ||||
|                             { | ||||
|                                 <MSubheader Class="mt-4 font-weight-black"> @item.Description </MSubheader> | ||||
|                                 <MTooltip Disabled=@(item.Remark==null||item.Remark?.IsNullOrEmpty()==true) Bottom Context="tip"> | ||||
|                                         <ActivatorContent> | ||||
|                                             <MTextField Type="@(item.PropertyName.Contains("Password") ? "password" : "text")" @attributes="@tip.Attrs" Dense Outlined HideDetails="@("auto")" @bind-Value=@item.Value /> | ||||
|                                         </ActivatorContent> | ||||
|                                         <ChildContent> | ||||
|                                             <span>@item.Remark</span> | ||||
|                                         </ChildContent> | ||||
|                                     </MTooltip> | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     </MCard> | ||||
|                 } | ||||
|             </MTabItem> | ||||
|         </MTabsItems> | ||||
|     </div> | ||||
|     ; | ||||
|         return renderFragment; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,161 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using BlazorComponent; | ||||
|  | ||||
| using Furion; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Masa.Blazor; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using ThingsGateway.Admin.Blazor; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
| using ThingsGateway.Application; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
| /// <summary> | ||||
| /// 采集设备页面 | ||||
| /// </summary> | ||||
| public partial class CollectDevicePage | ||||
| { | ||||
|     readonly CollectDevicePageInput search = new(); | ||||
|     IAppDataTable _datatable; | ||||
|     List<string> _deviceGroups = new(); | ||||
|     string _searchName; | ||||
|     List<CollectDevice> CollectDevices = new(); | ||||
|     List<DriverPluginCategory> DriverPlugins; | ||||
|     ImportExcel ImportExcel; | ||||
|     StringNumber tab; | ||||
|     [Inject] | ||||
|     AjaxService AjaxService { get; set; } | ||||
|     [CascadingParameter] | ||||
|     MainLayout MainLayout { get; set; } | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         CollectDevices = App.GetService<ICollectDeviceService>().GetCacheList(); | ||||
|         DriverPlugins = App.GetService<IDriverPluginService>().GetDriverPluginChildrenList(DriverEnum.Collect); | ||||
|         _deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList(); | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task AddCallAsync(CollectDeviceAddInput input) | ||||
|     { | ||||
|         await CollectDeviceService.AddAsync(input); | ||||
|         CollectDevices = CollectDeviceService.GetCacheList(); | ||||
|         _deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList(); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     async Task CopyDevAndVarAsync(IEnumerable<CollectDevice> data) | ||||
|     { | ||||
|         if (!data.Any()) | ||||
|         { | ||||
|             await PopupService.EnqueueSnackbarAsync("需选择一项或多项", AlertTypes.Warning); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         await CollectDeviceService.CopyDevAndVarAsync(data); | ||||
|         await DatatableQueryAsync(); | ||||
|         await PopupService.EnqueueSnackbarAsync("复制成功", AlertTypes.Success); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     async Task CopyDeviceAsync(IEnumerable<CollectDevice> data) | ||||
|     { | ||||
|         if (!data.Any()) | ||||
|         { | ||||
|             await PopupService.EnqueueSnackbarAsync("需选择一项或多项", AlertTypes.Warning); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         await CollectDeviceService.CopyDevAsync(data); | ||||
|         await DatatableQueryAsync(); | ||||
|         await PopupService.EnqueueSnackbarAsync("复制成功", AlertTypes.Success); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task DatatableQueryAsync() | ||||
|     { | ||||
|         await _datatable?.QueryClickAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task DeleteCallAsync(IEnumerable<CollectDevice> input) | ||||
|     { | ||||
|         await CollectDeviceService.DeleteAsync(input.Select(a => a.Id).ToArray()); | ||||
|         CollectDevices = CollectDeviceService.GetCacheList(); | ||||
|         _deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList(); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> DeviceImportAsync(IBrowserFile file) | ||||
|     { | ||||
|         return CollectDeviceService.PreviewAsync(file); | ||||
|     } | ||||
|     async Task DownExportAsync(CollectDevicePageInput input = null) | ||||
|     { | ||||
|         await AjaxService.DownFileAsync("gatewayFile/collectDevice", SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat(), input.Adapt<CollectDeviceInput>()); | ||||
|     } | ||||
|     private async Task DriverValueChangedAsync(CollectDeviceAddInput context, long pluginId) | ||||
|     { | ||||
|         if (pluginId <= 0) return; | ||||
|  | ||||
|         if (context.DevicePropertys == null || context.DevicePropertys?.Count == 0 || context.PluginId != pluginId) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 context.DevicePropertys = GetDriverProperties(pluginId, context.Id); | ||||
|                 await PopupService.EnqueueSnackbarAsync("插件附加属性已更新", AlertTypes.Success); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Error); | ||||
|             } | ||||
|         } | ||||
|         context.PluginId = pluginId; | ||||
|     } | ||||
|  | ||||
|     private async Task EditCallAsync(CollectDeviceEditInput input) | ||||
|     { | ||||
|         await CollectDeviceService.EditAsync(input); | ||||
|         CollectDevices = CollectDeviceService.GetCacheList(); | ||||
|         _deviceGroups = CollectDevices?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList(); | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     List<DependencyProperty> GetDriverProperties(long driverId, long devId) | ||||
|     { | ||||
|         return ServiceHelper.GetBackgroundService<CollectDeviceWorker>().GetDevicePropertys(driverId, devId); | ||||
|     } | ||||
|  | ||||
|     private async Task<SqlSugarPagedList<CollectDevice>> QueryCallAsync(CollectDevicePageInput input) | ||||
|     { | ||||
|         var data = await CollectDeviceService.PageAsync(input); | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     async Task SaveDeviceImportAsync(Dictionary<string, ImportPreviewOutputBase> data) | ||||
|     { | ||||
|         await CollectDeviceService.ImportAsync(data); | ||||
|         await DatatableQueryAsync(); | ||||
|         ImportExcel.IsShowImport = false; | ||||
|         await MainLayout.StateHasChangedAsync(); | ||||
|     } | ||||
| } | ||||
| @@ -1,162 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/gatewayconfig/config" | ||||
| @using System.Linq.Expressions; | ||||
| @using Masa.Blazor | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using ThingsGateway.Admin.Application; | ||||
| @using ThingsGateway.Admin.Blazor.Core; | ||||
| @using ThingsGateway.Admin.Blazor; | ||||
| @using ThingsGateway.Admin.Core; | ||||
| @using ThingsGateway.Application; | ||||
|  | ||||
| @inject IConfigService ConfigService | ||||
| @namespace ThingsGateway.Blazor | ||||
| @attribute [Authorize] | ||||
| @inject UserResoures UserResoures | ||||
| @inherits BaseComponentBase | ||||
| @layout MainLayout | ||||
| <MCard Height=@("100%") Elevation="1" Class="pa-2"> | ||||
|  | ||||
|     <MTabs @bind-Value="tab"> | ||||
|         <MTab Style="height:50px;" Value="1"> | ||||
|             报警转储配置 | ||||
|         </MTab> | ||||
|         <MTab Style="height:50px;" Value="2"> | ||||
|             历史数据配置 | ||||
|         </MTab> | ||||
|     </MTabs> | ||||
|  | ||||
|     <MTabsItems Value="tab"> | ||||
|         <MTabItem Value="1"> | ||||
|             @if (tab == 1) | ||||
|             { | ||||
|  | ||||
|                 <MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto") | ||||
|                    Flat Class="ml-2 my-4"> | ||||
|                     <MRow NoGutters> | ||||
|                     @foreach (var item in _alarmConfig) | ||||
|                         { | ||||
|                             switch (item.ConfigKey) | ||||
|                             { | ||||
|                                 case ThingsGatewayConfigConst.Config_Alarm_ConnStr: | ||||
|                                     <MCol Class="pa-2 px-4" Md=12 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue /> | ||||
|                                     </MCol> | ||||
|                                     break; | ||||
|                                 case ThingsGatewayConfigConst.Config_Alarm_DbType: | ||||
|                                     <MCol Class="pa-2 px-4" Md=12 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MSelect @bind-Value="item.ConfigValue" Outlined | ||||
|                                                  Items=@(typeof(SqlDbType).GetEnumList()) Clearable | ||||
|                                                  MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                                  ItemText=@((u) =>u.des) ItemValue=@(u =>u.name) | ||||
|                                                  HideDetails="true" Height="30" Dense> | ||||
|                                     </MSelect> | ||||
|                                 </MCol> | ||||
|                                     break; | ||||
|                                 case ThingsGatewayConfigConst.Config_Alarm_Enable: | ||||
|                                     <MCol Class="pa-2 px-4" Md=12 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MSelect @bind-Value=@item.ConfigValue Outlined | ||||
|                                                  Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                                  MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                                  ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u.ToString()) | ||||
|                                                  HideDetails=@("auto") Height="30" Dense> | ||||
|                                     </MSelect> | ||||
|  | ||||
|                                 </MCol> | ||||
|                                     break; | ||||
|                                 default: | ||||
|                                     <MCol Class="pa-2 px-4" Md=3 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue /> | ||||
|                                     </MCol> | ||||
|                                     break; | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     </MRow> | ||||
|                     <MCardActions> | ||||
|                         <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayalarmconfig")) Class="mt-8" OnClick=OnAlarmSaveAsync Color="primary"> | ||||
|                             保存 | ||||
|                         </MButton> | ||||
|                     </MCardActions> | ||||
|                 </MCard> | ||||
|             } | ||||
|         </MTabItem> | ||||
|         <MTabItem Value="2"> | ||||
|             @if (tab == 2) | ||||
|             { | ||||
|                 <MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto") | ||||
|                    Flat Class="ml-2 my-4"> | ||||
|                     <MRow NoGutters> | ||||
|                     @foreach (var item in _hisConfig) | ||||
|                         { | ||||
|                             switch (item.ConfigKey) | ||||
|                             { | ||||
|                                 case ThingsGatewayConfigConst.Config_His_ConnStr: | ||||
|                                     <MCol Class="pa-2 px-4" Md=12 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue /> | ||||
|                                     </MCol> | ||||
|                                     break; | ||||
|                                 case ThingsGatewayConfigConst.Config_His_DbType: | ||||
|                                     <MCol Class="pa-2 px-4" Md=12 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MSelect @bind-Value="item.ConfigValue" Outlined | ||||
|                                                  Items=@(typeof(HisDbType).GetEnumList()) Clearable | ||||
|                                                  MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                                  ItemText=@((u) =>u.des) ItemValue=@(u =>u.name) | ||||
|                                                  HideDetails="true" Height="30" Dense> | ||||
|                                     </MSelect> | ||||
|                                 </MCol> | ||||
|                                     break; | ||||
|                                 case ThingsGatewayConfigConst.Config_His_Enable: | ||||
|                                     <MCol Class="pa-2 px-4" Md=12 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MSelect @bind-Value=@item.ConfigValue Outlined | ||||
|                                                  Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                                  MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                                  ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u.ToString()) | ||||
|                                                  HideDetails=@("auto") Height="30" Dense> | ||||
|                                     </MSelect> | ||||
|  | ||||
|                                 </MCol> | ||||
|                                     break; | ||||
|                                 default: | ||||
|                                     <MCol Class="pa-2 px-4" Md=3 Cols="12"> | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @item.Remark</MSubheader> | ||||
|                                         <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@item.ConfigValue /> | ||||
|                                     </MCol> | ||||
|                                     break; | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     </MRow> | ||||
|                     <MCardActions> | ||||
|                         <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayhisconfig")) Class="mt-8" OnClick=OnHisSaveAsync Color="primary"> | ||||
|                             保存 | ||||
|                         </MButton> | ||||
|                     </MCardActions> | ||||
|                 </MCard> | ||||
|             } | ||||
|         </MTabItem> | ||||
|  | ||||
|     </MTabsItems> | ||||
|  | ||||
| </MCard> | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,76 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using BlazorComponent; | ||||
|  | ||||
| using Furion; | ||||
|  | ||||
| using Masa.Blazor; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
| using ThingsGateway.Application; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public partial class ConfigPage | ||||
| { | ||||
|     private List<SysConfig> _alarmConfig = new(); | ||||
|     private List<SysConfig> _hisConfig = new(); | ||||
|     StringNumber tab; | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public ConfigPage() | ||||
|     { | ||||
|         AlarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>(); | ||||
|         HistoryValueHostService = ServiceHelper.GetBackgroundService<HistoryValueWorker>(); | ||||
|     } | ||||
|  | ||||
|     AlarmWorker AlarmHostService { get; set; } | ||||
|  | ||||
|     HistoryValueWorker HistoryValueHostService { get; set; } | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         _alarmConfig = await App.GetService<IConfigService>().GetListByCategoryAsync(ThingsGatewayConfigConst.ThingGateway_AlarmConfig_Base); | ||||
|         _hisConfig = await App.GetService<IConfigService>().GetListByCategoryAsync(ThingsGatewayConfigConst.ThingGateway_HisConfig_Base); | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
|     private async Task OnAlarmSaveAsync() | ||||
|     { | ||||
|         var confirm = await PopupService.OpenConfirmDialogAsync("确认", "保存配置后将重启报警服务,是否确定?"); | ||||
|         if (confirm) | ||||
|         { | ||||
|             await ConfigService.EditBatchAsync(_alarmConfig); | ||||
|             await AlarmHostService.RestartAsync(); | ||||
|             await PopupService.EnqueueSnackbarAsync("成功", AlertTypes.Success); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task OnHisSaveAsync() | ||||
|     { | ||||
|         var confirm = await PopupService.OpenConfirmDialogAsync("确认", "保存配置后将重启历史服务,是否确定?"); | ||||
|         if (confirm) | ||||
|         { | ||||
|             await ConfigService.EditBatchAsync(_hisConfig); | ||||
|             await HistoryValueHostService.RestartAsync(); | ||||
|             await PopupService.EnqueueSnackbarAsync("成功", AlertTypes.Success); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,613 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/gatewayruntime/devicestatus" | ||||
| @namespace ThingsGateway.Blazor | ||||
| @using System.Linq.Expressions; | ||||
| @using BlazorComponent; | ||||
| @using Mapster; | ||||
| @using Masa.Blazor | ||||
| @using Masa.Blazor.Presets; | ||||
| @using System.IO; | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using ThingsGateway.Admin.Blazor.Core; | ||||
| @using ThingsGateway.Admin.Blazor; | ||||
| @using ThingsGateway.Admin.Core; | ||||
| @using ThingsGateway.Application; | ||||
| @using TouchSocket.Core; | ||||
| @inject ICollectDeviceService CollectDeviceService | ||||
| @inject IUploadDeviceService UploadDeviceService | ||||
|  | ||||
| @attribute [Authorize] | ||||
| @inherits BaseComponentBase | ||||
| @inject UserResoures UserResoures | ||||
| @inject NavigationManager NavigationManager | ||||
| @inject IDriverPluginService DriverPluginService | ||||
| @layout MainLayout | ||||
| <MSheet Style="overflow:auto"> | ||||
|  | ||||
|  | ||||
|     <MTabs @bind-Value="tab"> | ||||
|         <MTab Style="height:50px;" Value="1"> | ||||
|             采集设备状态 | ||||
|         </MTab> | ||||
|         <MTab Style="height:50px;" Value="2"> | ||||
|             上传设备状态 | ||||
|         </MTab> | ||||
|         <MTab Style="height:50px;" Value="3"> | ||||
|             其他服务状态 | ||||
|         </MTab> | ||||
|         <MButton Class="position-button" Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicerestart")) Color="red" Dark Fab Small Loading=isAllRestart OnClick="AllRestartAsync"> | ||||
|             <MIcon>   mdi-restart   </MIcon> | ||||
|         </MButton> | ||||
|     </MTabs> | ||||
|     <MTabsItems Value="tab"> | ||||
|         <MTabItem Value="1"> | ||||
|             @if (tab == 1) | ||||
|             { | ||||
|                 <MRow NoGutters> | ||||
|                 <MCol Md=2 Cols="12"> | ||||
|  | ||||
|                     <MCard Class="ma-2" Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px );")> | ||||
|                         <MCardTitle> | ||||
|                             <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_collectDeviceGroupSearchName" | ||||
|                                             Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) /> | ||||
|                             </MCardTitle> | ||||
|  | ||||
|                             <MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 240}px; overflow-y:auto)") Dense TItem="string" TKey="string" ActiveChanged=@(async a=> | ||||
|                                    { | ||||
|                                    if(_collectDeviceGroup!=a.FirstOrDefault()) | ||||
|                                    { | ||||
|                                    _collectDeviceGroup=a.FirstOrDefault(); CollectDeviceQuery(); | ||||
|                                    } | ||||
|                                    } ) | ||||
|                                        Items="_collectDeviceGroups" ItemText="r=>r" ItemChildren="r=>null" | ||||
|                                        Search="@_collectDeviceGroupSearchName" | ||||
|                                       Activatable ItemKey=@(r=>r)> | ||||
|                                 <LabelContent> | ||||
|                                     <span title=@context.Item> | ||||
|                                         @context.Item | ||||
|                                     </span> | ||||
|                                 </LabelContent> | ||||
|                             </MTreeview> | ||||
|                         </MCard> | ||||
|                     </MCol> | ||||
|                     <MCol Md=3 Cols="12"> | ||||
|                         <MCard Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px; )") Style="overflow-y:auto;" Flat Class="ml-2 my-4"> | ||||
|  | ||||
|                             <MVirtualScroll Context="item" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+100}px)") OverscanCount=2 ItemSize="60" Items="_collectDeviceCores"> | ||||
|  | ||||
|                                 <ItemContent> | ||||
|                                     @if (item.Device != null) | ||||
|                                     { | ||||
|  | ||||
|                                         <MListItem OnClick=@(()=>CollectDeviceInfo(item))> | ||||
|                                             <MListItemContent> | ||||
|                                                 <MListItemTitle> | ||||
|                                                     <MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")> | ||||
|                                                         <div class="mt-1  d-flex align-center justify-space-between" title=@item.Device?.Name> | ||||
|                                                             <span>@item.Device?.Name</span> | ||||
|                                                             <span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + "     " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span> | ||||
|                                                         </div> | ||||
|                                                     </MLabel> | ||||
|                                                 </MListItemTitle> | ||||
|                                             </MListItemContent> | ||||
|  | ||||
|                                         </MListItem> | ||||
|  | ||||
|                                         <MDivider></MDivider> | ||||
|  | ||||
|                                     } | ||||
|  | ||||
|                                 </ItemContent> | ||||
|                             </MVirtualScroll> | ||||
|  | ||||
|  | ||||
|                         </MCard> | ||||
|  | ||||
|                     </MCol> | ||||
|                     <MCol Md=7 Cols="12"> | ||||
|                         <MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px); overflow:auto)") Flat Elevation="0"> | ||||
|                             @if (collectDeviceInfoItem != null && collectDeviceInfoItem?.Device != null) | ||||
|                             { | ||||
|                                 var item = collectDeviceInfoItem; | ||||
|                                 <MCard Style="height:100px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0"> | ||||
|  | ||||
|                                     <MCardActions> | ||||
|                                         <div class="mr-12"></div> | ||||
|  | ||||
|                                         <MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")> | ||||
|                                             <div class="mt-1  d-flex align-center justify-space-between"> | ||||
|                                                 <span class="mx-3">@item.Device?.Name</span> | ||||
|                                                 <span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption mx-3">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + "     " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span> | ||||
|                                             </div> | ||||
|                                         </MLabel> | ||||
|  | ||||
|                                         <MSpacer></MSpacer> | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasPageWithRole("/gatewayruntime/devicevariable")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart | ||||
|                                                          OnClick=@(()=>NavigationManager.NavigateTo("/gatewayruntime/devicevariable?devicename="+item.Device?.Name))> | ||||
|                                                     <MIcon>mdi-information-outline</MIcon> | ||||
|                                                 </MButton> | ||||
|                                             </ActivatorContent> | ||||
|                                             <ChildContent> | ||||
|                                                 <span>相关变量</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|  | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small OnClick=@(()=>ConfigAsync(item.DeviceId,!item.Device?.KeepRun))> | ||||
|                                                     <MIcon>@(item.Device?.KeepRun == true ? "mdi-pause" : "mdi-play")</MIcon> | ||||
|                                                     </MButton> | ||||
|                                                 </ActivatorContent> | ||||
|                                                 <ChildContent> | ||||
|                                                     <span>@(item.Device?.KeepRun == true ? "暂停" : "运行")</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|                                         @*                                        <MTooltip Bottom Context="tip"> | ||||
|                                 <ActivatorContent> | ||||
|                                 <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicerestart")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart OnClick=@(()=>RestartAsync(item.DeviceId))> | ||||
|                                 <MIcon>mdi-restart</MIcon> | ||||
|                                 </MButton> | ||||
|                                 </ActivatorContent> | ||||
|                                 <ChildContent> | ||||
|                                 <span>重启</span> | ||||
|                                 </ChildContent> | ||||
|                                 </MTooltip>*@ | ||||
|  | ||||
|                                     </MCardActions> | ||||
|  | ||||
|  | ||||
|                                 </MCard> | ||||
|  | ||||
|                                 <MCard Style="height:200px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0"> | ||||
|  | ||||
|                                     <MSubheader> | ||||
|                                         运行状态 | ||||
|                                     </MSubheader> | ||||
|                                     <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.ActiveTime)</span> | ||||
|                                         <span class="text-caption">@item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.SourceVariableCount)</span> | ||||
|                                         <span class="text-caption">@item.Device?.SourceVariableCount</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.DeviceVariableCount)</span> | ||||
|                                         <span class="text-caption">@item.Device?.DeviceVariableCount</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.MethodVariableCount)</span> | ||||
|                                         <span class="text-caption">@item.Device?.MethodVariableCount</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=12 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.LastErrorMessage)</span> | ||||
|                                         <span class="text-caption red--text">@item.Device?.LastErrorMessage</span> | ||||
|                                     </MCol> | ||||
|                                 </MRow> | ||||
|  | ||||
|                                 <MSubheader> | ||||
|                                     配置信息 | ||||
|                                 </MSubheader> | ||||
|                                 <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.PluginName)</span> | ||||
|                                         <span class="text-caption">@item.Device?.PluginName</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.IsLogOut)</span> | ||||
|                                         <span class="text-caption">@item.Device?.IsLogOut</span> | ||||
|                                     </MCol> | ||||
|                                     @foreach (var property in item.Device?.DevicePropertys ?? new()) | ||||
|                                         { | ||||
|                                             <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                                 <span class="text-subtitle-2 grey--text">@property.Description</span> | ||||
|                                                 <span class="text-caption ">@(property.PropertyName.Contains("Password") ? "******" : @property.Value)</span> | ||||
|                                             </MCol> | ||||
|                                         } | ||||
|  | ||||
|                                     </MRow> | ||||
|  | ||||
|  | ||||
|                                 </MCard> | ||||
|  | ||||
|                             } | ||||
|  | ||||
|                             <MCard Flat Class="ml-4"> | ||||
|                                 @if (collectDeviceInfoItem != null && collectDeviceInfoItem?.Device != null) | ||||
|                                 { | ||||
|                                     <MCardActions> | ||||
|                                         报文日志(共享链路的日志也会相同) | ||||
|                                         <MSpacer></MSpacer> | ||||
|  | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small | ||||
|                                                          OnClick=@(()=> | ||||
|                                                  { | ||||
|                                                  pauseMessage=!pauseMessage; | ||||
|                                                  if(pauseMessage) | ||||
|                                                  CurMessages= collectDeviceInfoItem.Driver?.Messages.ToList(); | ||||
|                                                  } | ||||
|                                                  )> | ||||
|                                                     <MIcon>@((!pauseMessage) ? "mdi-pause" : "mdi-play")</MIcon> | ||||
|                                                     </MButton> | ||||
|                                                 </ActivatorContent> | ||||
|                                                 <ChildContent> | ||||
|                                                     <span>@((!pauseMessage) ? "暂停日志" : "运行日志")</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small | ||||
|                                                          OnClick=@(async()=> | ||||
|                                                  { | ||||
|                                                  var curMessages= collectDeviceInfoItem.Driver?.Messages.ToList(); | ||||
|                                                  await DownDeviceMessageExportAsync(curMessages); | ||||
|                                                  } | ||||
|                                                  )> | ||||
|                                                     <MIcon>mdi-export</MIcon> | ||||
|                                                 </MButton> | ||||
|                                             </ActivatorContent> | ||||
|                                             <ChildContent> | ||||
|                                                 <span>导出</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|                                     </MCardActions> | ||||
|                                     { | ||||
|                                         ICollection<string> item = null; | ||||
|                                         if (pauseMessage) | ||||
|                                         { | ||||
|                                             item = CurMessages; | ||||
|                                         } | ||||
|                                         else if (collectDeviceInfoItem.Driver != null) | ||||
|                                         { | ||||
|                                             item = collectDeviceInfoItem.Driver?.Messages; | ||||
|                                         } | ||||
|                                         if (item == null) | ||||
|                                         { | ||||
|                                             item = new List<string>(); | ||||
|                                         } | ||||
|                                         <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                                         <MVirtualScroll Context="itemMessage" TItem="string" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+480}px)") OverscanCount=2 ItemSize="100" Items="item"> | ||||
|                                             <ItemContent> | ||||
|                                                 <div title=@itemMessage style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;"> | ||||
|                                                     @itemMessage | ||||
|                                                 </div> | ||||
|                                             </ItemContent> | ||||
|                                         </MVirtualScroll> | ||||
|                                     </MRow> | ||||
|                                     } | ||||
|  | ||||
|                                 } | ||||
|                             </MCard> | ||||
|  | ||||
|                         </MCard> | ||||
|                     </MCol> | ||||
|                 </MRow> | ||||
|             } | ||||
|         </MTabItem> | ||||
|         <MTabItem Value="2"> | ||||
|             @if (tab == 2) | ||||
|             { | ||||
|                 <MRow> | ||||
|                     <MCol Md=2 Cols="12"> | ||||
|                         <MCard Class="ma-2" Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px); )")> | ||||
|                             <MCardTitle> | ||||
|                                 <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_uploadDeviceGroupSearchName" | ||||
|                                             Outlined Label=@typeof(UploadDevice).GetDescription(nameof(UploadDevice.DeviceGroup)) /> | ||||
|                             </MCardTitle> | ||||
|                             <MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+240}px);overflow-y:auto") Dense TItem="string" TKey="string" ActiveChanged=@(async a=> | ||||
|                                    { | ||||
|                                    if(_uploadDeviceGroup!=a.FirstOrDefault()) | ||||
|                                    { | ||||
|                                    _uploadDeviceGroup=a.FirstOrDefault(); UploadDeviceQuery(); | ||||
|                                    } | ||||
|                                    } ) | ||||
|                                        Items="_uploadDeviceGroups" ItemText="r=>r" ItemChildren="r=>null" | ||||
|                                        Search="@_uploadDeviceGroupSearchName" | ||||
|                                       Activatable ItemKey=@(r=>r)> | ||||
|                                 <LabelContent> | ||||
|                                     <span title=@context.Item> | ||||
|                                         @context.Item | ||||
|                                     </span> | ||||
|                                 </LabelContent> | ||||
|                             </MTreeview> | ||||
|                         </MCard> | ||||
|                     </MCol> | ||||
|                     <MCol Md=3 Cols="12"> | ||||
|                         <MCard Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+80}px)") Style="overflow-y:auto;" Flat Class="ml-2 my-4"> | ||||
|  | ||||
|                             <MVirtualScroll Context="item" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+100}px)") OverscanCount=2 ItemSize="60" Items="_uploadDeviceCores"> | ||||
|  | ||||
|                                 <ItemContent> | ||||
|                                     @if (item.Device != null) | ||||
|                                     { | ||||
|  | ||||
|                                         <MListItem OnClick=@(()=>UploadDeviceInfo(item))> | ||||
|                                             <MListItemContent> | ||||
|                                                 <MListItemTitle> | ||||
|                                                     <MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")> | ||||
|                                                         <div class="mt-1  d-flex align-center justify-space-between" title=@item.Device?.Name> | ||||
|                                                             <span>@item.Device?.Name</span> | ||||
|                                                             <span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + "     " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span> | ||||
|                                                         </div> | ||||
|                                                     </MLabel> | ||||
|                                                 </MListItemTitle> | ||||
|                                             </MListItemContent> | ||||
|  | ||||
|                                         </MListItem> | ||||
|  | ||||
|                                         <MDivider></MDivider> | ||||
|  | ||||
|                                     } | ||||
|  | ||||
|                                 </ItemContent> | ||||
|                             </MVirtualScroll> | ||||
|  | ||||
|  | ||||
|                         </MCard> | ||||
|  | ||||
|                     </MCol> | ||||
|                     <MCol Md=7 Cols="12"> | ||||
|                         <MCard Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight + 80}px); overflow:auto)") Flat Elevation="0"> | ||||
|                             @if (uploadDeviceInfoItem != null && uploadDeviceInfoItem?.Device != null) | ||||
|                             { | ||||
|                                 var item = uploadDeviceInfoItem; | ||||
|                                 <MCard Style="height:100px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0"> | ||||
|  | ||||
|                                     <MCardActions> | ||||
|                                         <div class="mr-12"></div> | ||||
|  | ||||
|                                         <MLabel Class=@((item.Device?.DeviceStatus==DeviceStatusEnum.OnLine?"green--text":"red--text")+$" text-h6")> | ||||
|                                             <div class="mt-1  d-flex align-center justify-space-between"> | ||||
|                                                 <span class="mx-3">@item.Device?.Name</span> | ||||
|                                                 <span style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;" class="text-caption mx-3">@(item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + "     " + typeof(DeviceStatusEnum).GetDescription(item.Device?.DeviceStatus.ToString()))</span> | ||||
|                                             </div> | ||||
|                                         </MLabel> | ||||
|  | ||||
|                                         <MSpacer></MSpacer> | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasPageWithRole("/gatewayruntime/devicevariable")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart | ||||
|                                                          OnClick=@(()=>NavigationManager.NavigateTo("/gatewayruntime/devicevariable?uploaddevicename="+item.Device?.Name))> | ||||
|                                                     <MIcon>mdi-information-outline</MIcon> | ||||
|                                                 </MButton> | ||||
|                                             </ActivatorContent> | ||||
|                                             <ChildContent> | ||||
|                                                 <span>相关变量</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|  | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small OnClick=@(()=>UpConfigAsync(item.DeviceId,!item.Device?.KeepRun))> | ||||
|                                                     <MIcon>@(item.Device?.KeepRun == true ? "mdi-pause" : "mdi-play")</MIcon> | ||||
|                                                     </MButton> | ||||
|                                                 </ActivatorContent> | ||||
|                                                 <ChildContent> | ||||
|                                                     <span>@(item.Device?.KeepRun == true ? "暂停" : "运行")</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicerestart")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small Loading=isRestart OnClick=@(()=> UpRestartAsync(item.DeviceId))> | ||||
|                                                     <MIcon>mdi-restart</MIcon> | ||||
|                                                 </MButton> | ||||
|                                             </ActivatorContent> | ||||
|                                             <ChildContent> | ||||
|                                                 <span>重启</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|                                     </MCardActions> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|                                 </MCard> | ||||
|                                 <MCard Style="height:200px;overflow:auto;" Flat Class="ml-4 my-4 ma-2" Elevation="0"> | ||||
|  | ||||
|  | ||||
|                                     <MSubheader> | ||||
|                                         运行状态 | ||||
|                                     </MSubheader> | ||||
|                                     <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.ActiveTime)</span> | ||||
|                                         <span class="text-caption">@item.Device?.ActiveTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.UploadVariableCount)</span> | ||||
|                                         <span class="text-caption">@item.Device?.UploadVariableCount</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=12 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.LastErrorMessage)</span> | ||||
|                                         <span class="text-caption red--text">@item.Device?.LastErrorMessage</span> | ||||
|                                     </MCol> | ||||
|                                 </MRow> | ||||
|  | ||||
|                                 <MSubheader> | ||||
|                                     配置信息 | ||||
|                                 </MSubheader> | ||||
|                                 <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.PluginName)</span> | ||||
|                                         <span class="text-caption">@item.Device?.PluginName</span> | ||||
|                                     </MCol> | ||||
|                                     <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                         <span class="text-subtitle-2 grey--text">@item.Device?.Description(a=>a.IsLogOut)</span> | ||||
|                                         <span class="text-caption">@item.Device?.IsLogOut</span> | ||||
|                                     </MCol> | ||||
|                                     @foreach (var property in item.Device?.DevicePropertys ?? new()) | ||||
|                                         { | ||||
|                                             <MCol Md=6 Cols="12" Class="px-4 mt-1  d-flex align-center justify-space-between"> | ||||
|                                                 <span class="text-subtitle-2 grey--text">@property.Description</span> | ||||
|                                                 <span class="text-caption ">@(property.PropertyName.Contains("Password") ? "******" : @property.Value)</span> | ||||
|                                             </MCol> | ||||
|                                         } | ||||
|  | ||||
|                                     </MRow> | ||||
|  | ||||
|  | ||||
|                                 </MCard> | ||||
|                             } | ||||
|  | ||||
|                             <MCard Flat Class="ml-4"> | ||||
|                                 @if (uploadDeviceInfoItem != null && uploadDeviceInfoItem?.Device != null) | ||||
|                                 { | ||||
|                                     <MCardActions> | ||||
|                                         报文日志(共享链路的日志也会相同) | ||||
|                                         <MSpacer></MSpacer> | ||||
|  | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small | ||||
|                                                          OnClick=@(()=> | ||||
|                                                  { | ||||
|                                                  pauseMessage=!pauseMessage; | ||||
|                                                  if(pauseMessage) | ||||
|                                                  CurMessages= uploadDeviceInfoItem.Driver?.Messages.ToList(); | ||||
|                                                  } | ||||
|                                                  )> | ||||
|                                                     <MIcon>@((!pauseMessage) ? "mdi-pause" : "mdi-play")</MIcon> | ||||
|                                                     </MButton> | ||||
|                                                 </ActivatorContent> | ||||
|                                                 <ChildContent> | ||||
|                                                     <span>@((!pauseMessage) ? "暂停日志" : "运行日志")</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|                                         <MTooltip Bottom Context="tip"> | ||||
|                                             <ActivatorContent> | ||||
|                                                 <MButton Loading=isDownExport Disabled=@(!UserResoures.IsHasButtonWithRole("gatewaydevicepause")) Class="mx-2" @attributes="@tip.Attrs" Dark Fab Small | ||||
|                                                          OnClick=@(async()=> | ||||
|                                                  { | ||||
|                                                  var curMessages= uploadDeviceInfoItem.Driver?.Messages.ToList(); | ||||
|                                                  await DownDeviceMessageExportAsync(curMessages); | ||||
|                                                  } | ||||
|                                                  )> | ||||
|                                                     <MIcon>mdi-export</MIcon> | ||||
|                                                 </MButton> | ||||
|                                             </ActivatorContent> | ||||
|                                             <ChildContent> | ||||
|                                                 <span>导出</span> | ||||
|                                             </ChildContent> | ||||
|                                         </MTooltip> | ||||
|  | ||||
|                                     </MCardActions> | ||||
|                                     { | ||||
|                                         ICollection<string> item = null; | ||||
|                                         if (pauseMessage) | ||||
|                                         { | ||||
|                                             item = CurMessages; | ||||
|                                         } | ||||
|                                         else if (uploadDeviceInfoItem.Driver != null) | ||||
|                                         { | ||||
|                                             item = uploadDeviceInfoItem.Driver?.Messages ?? new(); | ||||
|                                         } | ||||
|                                         if (item == null) | ||||
|                                         { | ||||
|                                             item = new List<string>(); | ||||
|                                         } | ||||
|                                         <MRow Class="ml-2 mr-2 d-flex" NoGutters> | ||||
|                                         <MVirtualScroll Context="itemMessage" TItem="string" Height=@($"calc(100vh - {BlazorResourceConst.DefaultHeight+480}px)") OverscanCount=2 ItemSize="100" Items="item"> | ||||
|                                             <ItemContent> | ||||
|                                                 <div title=@itemMessage style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;"> | ||||
|                                                     @itemMessage | ||||
|                                                 </div> | ||||
|                                             </ItemContent> | ||||
|                                         </MVirtualScroll> | ||||
|                                     </MRow> | ||||
|                                     } | ||||
|  | ||||
|                                 } | ||||
|                             </MCard> | ||||
|  | ||||
|                         </MCard> | ||||
|                     </MCol> | ||||
|                 </MRow> | ||||
|             } | ||||
|         </MTabItem> | ||||
|  | ||||
|         <MTabItem Value="3"> | ||||
|             @if (tab == 3) | ||||
|             { | ||||
|                 <MRow NoGutters> | ||||
|                 <MCard Class="ml-2 my-3" Style="width:100%" Elevation="1"> | ||||
|                     <MCardSubtitle Class=@((AlarmHostService.StatuString.IsSuccess?"green--text":"red--text")+$" text-subtitle-2")> | ||||
|                         <div class="mt-1  d-flex align-center justify-space-between"> | ||||
|                             <span>历史报警服务状态</span> | ||||
|                             <span class="text-caption">@AlarmHostService.StatuString.Message</span> | ||||
|                         </div> | ||||
|                     </MCardSubtitle> | ||||
|                 </MCard> | ||||
|             </MRow> | ||||
|                 <MRow NoGutters> | ||||
|                 <MCard Class="ml-2 my-3" Style="width:100%" Elevation="1"> | ||||
|                     <MCardSubtitle Class=@((HistoryValueHostService.StatuString.IsSuccess?"green--text":"red--text")+$" text-subtitle-2")> | ||||
|                         <div class="mt-1  d-flex align-center justify-space-between"> | ||||
|                             <span>历史数据服务状态</span> | ||||
|                             <span class="text-caption">@HistoryValueHostService.StatuString.Message</span> | ||||
|                         </div> | ||||
|                     </MCardSubtitle> | ||||
|                 </MCard> | ||||
|             </MRow> | ||||
|                 <MRow NoGutters> | ||||
|                 <MCard Class="ml-2 my-3" Style="width:100%" Elevation="1"> | ||||
|                     <MCardSubtitle Class=@((MemoryVariableWorker.StatuString.IsSuccess?"green--text":"red--text")+$" text-subtitle-2")> | ||||
|                         <div class="mt-1  d-flex align-center justify-space-between"> | ||||
|                             <span>中间变量计算服务状态</span> | ||||
|                             <span class="text-caption">@MemoryVariableWorker.StatuString.Message</span> | ||||
|                         </div> | ||||
|                     </MCardSubtitle> | ||||
|                 </MCard> | ||||
|             </MRow> | ||||
|  | ||||
|             } | ||||
|  | ||||
|         </MTabItem> | ||||
|     </MTabsItems> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| </MSheet> | ||||
| <style> | ||||
|     .position-button { | ||||
|         position: fixed !important; | ||||
|         top: 10%; | ||||
|         right: 0; | ||||
|         box-shadow: 1px 1px 8px var(--mud-palette-primary); | ||||
|         background-color: var(--mud-palette-primary); | ||||
|         cursor: pointer | ||||
|     } | ||||
|  | ||||
| </style> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,276 +0,0 @@ | ||||
| #region copyright | ||||
| //------------------------------------------------------------------------------ | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> | ||||
| //  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD> | ||||
| //  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD> | ||||
| //  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway | ||||
| //  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway | ||||
| //  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQȺ<51><C8BA>605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| #endregion | ||||
|  | ||||
| using BlazorComponent; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Masa.Blazor; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using Microsoft.JSInterop; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
|  | ||||
| using ThingsGateway.Admin.Blazor; | ||||
| using ThingsGateway.Admin.Blazor.Core; | ||||
| using ThingsGateway.Admin.Core; | ||||
| using ThingsGateway.Application; | ||||
|  | ||||
| namespace ThingsGateway.Blazor; | ||||
|  | ||||
| /// <summary> | ||||
| /// <20>豸״̬ҳ<CCAC><D2B3> | ||||
| /// </summary> | ||||
| public partial class DeviceStatusPage : IDisposable | ||||
| { | ||||
|     readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(1)); | ||||
|     List<CollectDeviceCore> _collectDeviceCores = new(); | ||||
|     private string _collectDeviceGroup; | ||||
|     List<string> _collectDeviceGroups = new(); | ||||
|     string _collectDeviceGroupSearchName; | ||||
|     List<UploadDeviceCore> _uploadDeviceCores = new(); | ||||
|     private string _uploadDeviceGroup; | ||||
|     List<string> _uploadDeviceGroups = new(); | ||||
|     string _uploadDeviceGroupSearchName; | ||||
|     CollectDeviceCore collectDeviceInfoItem; | ||||
|     List<string> CurMessages = new(); | ||||
|     bool isAllRestart; | ||||
|     private bool isDownExport; | ||||
|     bool isRestart; | ||||
|     bool pauseMessage; | ||||
|     StringNumber tab; | ||||
|     UploadDeviceCore uploadDeviceInfoItem; | ||||
|     AlarmWorker AlarmHostService { get; set; } | ||||
|     CollectDeviceWorker CollectDeviceHostService { get; set; } | ||||
|     [Inject] | ||||
|     GlobalDeviceData GlobalDeviceData { get; set; } | ||||
|  | ||||
|     IJSObjectReference Helper { get; set; } | ||||
|     HistoryValueWorker HistoryValueHostService { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     InitTimezone InitTimezone { get; set; } | ||||
|     [Inject] | ||||
|     IJSRuntime JS { get; set; } | ||||
|     [CascadingParameter] | ||||
|     MainLayout MainLayout { get; set; } | ||||
|  | ||||
|     MemoryVariableWorker MemoryVariableWorker { get; set; } | ||||
|     StringNumber Panel { get; set; } | ||||
|  | ||||
|     UploadDeviceWorker UploadDeviceHostService { get; set; } | ||||
|     StringNumber Uppanel { get; set; } | ||||
|     [Inject] | ||||
|     IVariableService VariableService { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     public override void Dispose() | ||||
|     { | ||||
|         _periodicTimer?.Dispose(); | ||||
|         base.Dispose(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         CollectDeviceHostService = ServiceHelper.GetBackgroundService<CollectDeviceWorker>(); | ||||
|         UploadDeviceHostService = ServiceHelper.GetBackgroundService<UploadDeviceWorker>(); | ||||
|         AlarmHostService = ServiceHelper.GetBackgroundService<AlarmWorker>(); | ||||
|         HistoryValueHostService = ServiceHelper.GetBackgroundService<HistoryValueWorker>(); | ||||
|         MemoryVariableWorker = ServiceHelper.GetBackgroundService<MemoryVariableWorker>(); | ||||
|  | ||||
|         _ = RunTimerAsync(); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// <inheritdoc/> | ||||
|     /// </summary> | ||||
|     protected override void OnParametersSet() | ||||
|     { | ||||
|         CollectDeviceQuery(); | ||||
|         UploadDeviceQuery(); | ||||
|         base.OnParametersSet(); | ||||
|     } | ||||
|     async Task AllRestartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var confirm = await PopupService.OpenConfirmDialogAsync("<22><><EFBFBD><EFBFBD>", "ȷ<><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?"); | ||||
|             if (confirm) | ||||
|             { | ||||
|                 isAllRestart = true; | ||||
|                 StateHasChanged(); | ||||
|                 PopupService.ShowProgressLinear(); | ||||
|                 await Task.Run(async () => await CollectDeviceHostService.RestartDeviceThreadAsync()); | ||||
|                 CollectDeviceQuery(); | ||||
|                 UploadDeviceQuery(); | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             collectDeviceInfoItem = null; | ||||
|             uploadDeviceInfoItem = null; | ||||
|             isAllRestart = false; | ||||
|             PopupService.HideProgressLinear(); | ||||
|             await MainLayout.StateHasChangedAsync(); | ||||
|         } | ||||
|     } | ||||
|     void CollectDeviceInfo(CollectDeviceCore item) | ||||
|     { | ||||
|         collectDeviceInfoItem = item; | ||||
|         CurMessages = new(); | ||||
|     } | ||||
|     void CollectDeviceQuery() | ||||
|     { | ||||
|         _collectDeviceGroups = GlobalDeviceData.CollectDevices.Adapt<List<CollectDevice>>()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new(); | ||||
|         _collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIF(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _collectDeviceGroup).ToList() ?? new(); | ||||
|     } | ||||
|     async Task ConfigAsync(long devId, bool? isStart) | ||||
|     { | ||||
|         var str = isStart == true ? "<22><><EFBFBD><EFBFBD>" : "<22><>ͣ"; | ||||
|         var confirm = await PopupService.OpenConfirmDialogAsync(str, $"ȷ<><C8B7>{str}?"); | ||||
|         if (confirm) | ||||
|         { | ||||
|             CollectDeviceHostService.ConfigDeviceThread(devId, isStart == true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async Task DownDeviceMessageExportAsync(List<string> values) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             isDownExport = true; | ||||
|             StateHasChanged(); | ||||
|             using var memoryStream = new MemoryStream(); | ||||
|             StreamWriter writer = new(memoryStream); | ||||
|             foreach (var item in values) | ||||
|             { | ||||
|                 writer.WriteLine(item); | ||||
|             } | ||||
|  | ||||
|             writer.Flush(); | ||||
|             memoryStream.Seek(0, SeekOrigin.Begin); | ||||
|             using var streamRef = new DotNetStreamReference(stream: memoryStream); | ||||
|             Helper ??= await JS.InvokeAsync<IJSObjectReference>("import", $"/_content/ThingsGateway.Admin.Blazor.Core/js/downloadFileFromStream.js"); | ||||
|             await Helper.InvokeVoidAsync("downloadFileFromStream", $"<22><><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>{SysDateTimeExtensions.CurrentDateTime.ToFileDateTimeFormat()}.txt", streamRef); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isDownExport = false; | ||||
|         } | ||||
|     } | ||||
|     //ȥ<><C8A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD><EFBFBD> | ||||
|     //async Task RestartAsync(long devId) | ||||
|     //{ | ||||
|     //    try | ||||
|     //    { | ||||
|     //        var confirm = await PopupService.OpenConfirmDialogAsync("<22><><EFBFBD><EFBFBD>", "ȷ<><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?"); | ||||
|     //        if (confirm) | ||||
|     //        { | ||||
|     //            isRestart = true; | ||||
|     //            StateHasChanged(); | ||||
|     //            await Task.Run(async () => await CollectDeviceHostService.UpDeviceThreadAsync(devId)); | ||||
|     //            collectDeviceInfoItem = null; | ||||
|     //            CollectDeviceQuery(); | ||||
|     //        } | ||||
|     //    } | ||||
|     //    catch (Exception ex) | ||||
|     //    { | ||||
|     //        await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Warning); | ||||
|     //    } | ||||
|     //    finally | ||||
|     //    { | ||||
|     //        isRestart = false; | ||||
|     //        await MainLayout.StateHasChangedAsync(); | ||||
|     //    } | ||||
|     //} | ||||
|     private async Task RunTimerAsync() | ||||
|     { | ||||
|         while (await _periodicTimer.WaitForNextTickAsync()) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 { | ||||
|                     _collectDeviceGroups = GlobalDeviceData.CollectDevices.Adapt<List<CollectDevice>>()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new(); | ||||
|                     _collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIF(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _collectDeviceGroup).ToList() ?? new(); | ||||
|                 } | ||||
|                 if (_collectDeviceCores?.FirstOrDefault()?.Device == null || CollectDeviceHostService?.CollectDeviceCores.Count != _collectDeviceCores.Count) | ||||
|                 { | ||||
|                     CollectDeviceQuery(); | ||||
|                 } | ||||
|  | ||||
|                 if (_uploadDeviceCores?.FirstOrDefault()?.Device == null || UploadDeviceHostService?.UploadDeviceCores.Count != _uploadDeviceCores.Count) | ||||
|                 { | ||||
|                     UploadDeviceQuery(); | ||||
|                 } | ||||
|  | ||||
|                 await InvokeAsync(StateHasChanged); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     async Task UpConfigAsync(long devId, bool? isStart) | ||||
|     { | ||||
|         var str = isStart == true ? "<22><><EFBFBD><EFBFBD>" : "<22><>ͣ"; | ||||
|         var confirm = await PopupService.OpenConfirmDialogAsync(str, $"ȷ<><C8B7>{str}?"); | ||||
|         if (confirm) | ||||
|         { | ||||
|             UploadDeviceHostService.ConfigDeviceThread(devId, isStart == true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void UploadDeviceInfo(UploadDeviceCore item) | ||||
|     { | ||||
|         uploadDeviceInfoItem = item; | ||||
|     } | ||||
|  | ||||
|     void UploadDeviceQuery() | ||||
|     { | ||||
|         _uploadDeviceGroups = UploadDeviceHostService.UploadDeviceCores.Select(a => a.Device)?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new(); | ||||
|         _uploadDeviceCores = UploadDeviceHostService?.UploadDeviceCores?.WhereIF(!_uploadDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _uploadDeviceGroup).ToList() ?? new(); | ||||
|     } | ||||
|     async Task UpRestartAsync(long devId) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var confirm = await PopupService.OpenConfirmDialogAsync("<22><><EFBFBD><EFBFBD>", "ȷ<><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?"); | ||||
|             if (confirm) | ||||
|             { | ||||
|                 isRestart = true; | ||||
|                 StateHasChanged(); | ||||
|                 await Task.Run(async () => await UploadDeviceHostService.UpDeviceThreadAsync(devId)); | ||||
|                 uploadDeviceInfoItem = null; | ||||
|                 UploadDeviceQuery(); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await PopupService.EnqueueSnackbarAsync(ex.Message, AlertTypes.Warning); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             isRestart = false; | ||||
|             await MainLayout.StateHasChangedAsync(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,663 +0,0 @@ | ||||
| @* | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| *@ | ||||
|  | ||||
| @page "/gatewayconfig/devicevariable" | ||||
| @namespace ThingsGateway.Blazor | ||||
| @using System.Linq.Expressions; | ||||
| @using BlazorComponent; | ||||
| @using Furion.DataValidation; | ||||
| @using Furion; | ||||
| @using Mapster; | ||||
| @using Masa.Blazor | ||||
| @using Masa.Blazor.Presets; | ||||
| @using System.IO; | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using ThingsGateway.Admin.Blazor.Core; | ||||
| @using ThingsGateway.Admin.Blazor; | ||||
| @using ThingsGateway.Admin.Core; | ||||
| @using ThingsGateway.Application; | ||||
| @attribute [Authorize] | ||||
| @inherits BaseComponentBase | ||||
| @inject UserResoures UserResoures | ||||
| @layout MainLayout | ||||
|  | ||||
| @if (IsMobile) | ||||
| { | ||||
|     @GetAppDataTable() | ||||
|  | ||||
| } | ||||
| else | ||||
| { | ||||
|     <MRow> | ||||
|         <MCol Md=2 Cols="12"> | ||||
|             <MCard Class="ma-2" Height=@("100%")> | ||||
|                 <MCardTitle> | ||||
|                     <MTextField Dense Style="max-width:200px;" HideDetails=@("auto") Class="mx-2 my-1" @bind-Value="_searchName" | ||||
|                                 Outlined Label=@typeof(CollectDevice).GetDescription(nameof(CollectDevice.DeviceGroup)) /> | ||||
|                 </MCardTitle> | ||||
|                 <MTreeview Style=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+100}px);overflow-y:auto") Dense TItem="DeviceTree" TKey="string" OpenOnClick ActiveChanged=@(async a=> | ||||
|                        { | ||||
|                        if(search.DeviceName!=a.FirstOrDefault()) | ||||
|                        { | ||||
|                        search.DeviceName=a.FirstOrDefault(); | ||||
|                        await DatatableQueryAsync(); | ||||
|                        } | ||||
|                        } ) | ||||
|                            Items="_deviceGroups" ItemText="r=>r.Name" ItemChildren="r=>r.Childrens" | ||||
|                            Search="@_searchName" | ||||
|                           Activatable ItemKey=@(r=>r.Name)> | ||||
|                     <LabelContent> | ||||
|                         <span title=@context.Item.Name> | ||||
|                             @context.Item.Name | ||||
|                         </span> | ||||
|                     </LabelContent> | ||||
|                 </MTreeview> | ||||
|             </MCard> | ||||
|         </MCol> | ||||
|         <MCol Md=10 Cols="12"> | ||||
|  | ||||
|             @GetAppDataTable() | ||||
|  | ||||
|         </MCol> | ||||
|     </MRow> | ||||
| } | ||||
|  | ||||
| <ImportExcel @ref=ImportExcel Import="SaveDeviceImportAsync" Preview="DeviceImportAsync" /> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @code { | ||||
|     RenderFragment GetAppDataTable() | ||||
|     { | ||||
|         RenderFragment renderFragment = | ||||
|     @<AppDataTable @ref="_datatable" | ||||
|                        StyleString=@($"height: calc(100vh - {BlazorResourceConst.DefaultHeight+10}px)") | ||||
|                        TItem="DeviceVariable" SearchItem="DeviceVariablePageInput" | ||||
|                        AddItem="DeviceVariableAddInput" EditItem="VariableEditInput" | ||||
|                        IsMenuOperTemplate=false SearchModel="search" | ||||
|                        QueryCallAsync="QueryCallAsync" AddCallAsync="AddCallAsync" | ||||
|                        EditCallAsync="EditCallAsync" DeleteCallAsync="DeleteCallAsync" | ||||
|                                   IsShowDetailButton | ||||
|                                   IsShowQueryButton | ||||
|                        IsShowAddButton=@UserResoures.IsHasButtonWithRole("gatewayvariableadd") | ||||
|                        IsShowDeleteButton=@UserResoures.IsHasButtonWithRole("gatewayvariabledelete") | ||||
|                        IsShowEditButton=@UserResoures.IsHasButtonWithRole("gatewayvariableedit")> | ||||
|         <SearchTemplate> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.Name" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.Name) /> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.VariableAddress" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.VariableAddress) /> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.DeviceName" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.DeviceName) /> | ||||
|             <MTextField Dense | ||||
|                         Style="max-width:200px;" HideDetails=@("auto") Class="my-1  mx-2 " @bind-Value="context.UploadDeviceName" | ||||
|                                 Clearable | ||||
|                                 Outlined | ||||
|                         Label=@context.Description(x => x.UploadDeviceName) /> | ||||
|         </SearchTemplate> | ||||
|         <OtherToolbarTemplate> | ||||
|  | ||||
|             <MMenu OffsetY | ||||
|                    Context="menu"> | ||||
|                 <ActivatorContent> | ||||
|                     <MButton @attributes="@menu.Attrs" Color="primary" | ||||
|                              Class="my-1  mx-2 "> | ||||
|                         导出 | ||||
|                         <AppChevronDown></AppChevronDown> | ||||
|                     </MButton> | ||||
|                 </ActivatorContent> | ||||
|                 <ChildContent> | ||||
|                     <MList> | ||||
|                         <MListItem OnClick="()=>DownExportAsync()"> 导出全部 </MListItem> | ||||
|                         <MListItem OnClick="()=>DownExportAsync(search)">  导出搜索项 </MListItem> | ||||
|                     </MList> | ||||
|  | ||||
|                 </ChildContent> | ||||
|             </MMenu> | ||||
|  | ||||
|  | ||||
|  | ||||
|             <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayvariableedit")) Class="my-1  mx-2" OnClick="()=>{ ImportExcel.Step=1;  ImportExcel.IsShowImport=true;}" Color="primary"> | ||||
|                 导入 | ||||
|             </MButton> | ||||
|             <MButton Disabled=@(!UserResoures.IsHasButtonWithRole("gatewayvariableedit")) Class="my-1  mx-2" OnClick=ClearAsync Color="primary"> | ||||
|                 清空 | ||||
|             </MButton> | ||||
|         </OtherToolbarTemplate> | ||||
|         <AddTemplate> | ||||
|             @{ | ||||
|                 var data = CollectDevices.FirstOrDefault(); | ||||
|                 context.DeviceId = context.DeviceId == 0 ? data == null ? 0 : data.Id : context.DeviceId; | ||||
|             } | ||||
|             @GetRenderFragment(context) | ||||
|         </AddTemplate> | ||||
|         <EditTemplate> | ||||
|             @GetRenderFragment(context) | ||||
|         </EditTemplate> | ||||
|         <ItemColTemplate> | ||||
|             @switch (context.Header.Value) | ||||
|             { | ||||
|                 case nameof(context.Item.DeviceId): | ||||
|                     <span title=@context.Value> | ||||
|                         @( | ||||
|                             App.GetService<ICollectDeviceService>().GetNameById(context.Item.DeviceId) | ||||
|                                 ) | ||||
|                     </span> | ||||
|                     break; | ||||
|                 default: | ||||
|                     @if (context.Header.CellClass?.Contains("text-truncate") == true) | ||||
|                     { | ||||
|                         <span title=@context.Value> | ||||
|                             @context.Value | ||||
|                         </span> | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         @context.Value | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|             } | ||||
|  | ||||
|         </ItemColTemplate> | ||||
|  | ||||
|         <Detailemplate> | ||||
|             @{ | ||||
|                 switch (context.Item1.Value) | ||||
|                 { | ||||
|                     case nameof(DeviceVariable.DeviceId): | ||||
|                         <tr @key="context.Item1.Text"> | ||||
|                             <td class="text-start table-minwidth"> | ||||
|                                 @context.Item1.Text | ||||
|                             </td> | ||||
|                             <td class="text-start "> | ||||
|                                 <div style="word-break:break-all; white-space:pre-wrap;"> | ||||
|                                     @(App.GetService<ICollectDeviceService>().GetNameById(context.Item2.ToLong())) | ||||
|                                 </div> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                         break; | ||||
|                     default: | ||||
|                         <tr @key="context.Item1.Text"> | ||||
|                             <td class="text-start table-minwidth"> | ||||
|                                 @context.Item1.Text | ||||
|                             </td> | ||||
|                             <td class="text-start "> | ||||
|                                 <div style="word-break:break-all; white-space:pre-wrap;"> | ||||
|                                     @context.Item2 | ||||
|                                 </div> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                         break; | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         </Detailemplate> | ||||
|     </AppDataTable> | ||||
|         ; | ||||
|         return renderFragment; | ||||
|     } | ||||
|  | ||||
|     RenderFragment GetRenderFragment(VariableEditInput context) | ||||
|     { | ||||
|         if (!OtherMethods.ContainsKey(context.DeviceId)) | ||||
|         { | ||||
|             DeviceChanged(context.DeviceId); | ||||
|         } | ||||
|  | ||||
|         RenderFragment renderFragment = | ||||
|     @<div> | ||||
|         <MTabs @bind-Value="tab"> | ||||
|             <MTab Value=1 Style="height:50px;">   基本属性    </MTab> | ||||
|             <MTab Value=2 Style="height:50px;">    报警属性    </MTab> | ||||
|             <MTab Value=3 Style="height:50px;">    历史属性    </MTab> | ||||
|             <MTab Value=4 Style="height:50px;">    上传属性    </MTab> | ||||
|         </MTabs> | ||||
|  | ||||
|         <MTabsItems Value="tab"> | ||||
|             <MTabItem Value="1"> | ||||
|                 @if (tab == 1) | ||||
|                 { | ||||
|  | ||||
|                     <MCard Flat Class="ma-2"> | ||||
|                             <MRow NoGutters Class="my-2" Justify="JustifyTypes.Start"> | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.Name))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Name /> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black">    @(context.Description(x => x.Description))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Description /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black">    @context.Description(x=>x.DeviceId)  </MSubheader> | ||||
|                                     <MSelect Class="mr-3" @bind-Value=@context.DeviceId Outlined | ||||
|                                              Items=@(CollectDevices) OnClick=@(()=>DeviceChanged(context.DeviceId)) | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id) | ||||
|                                              ItemDisabled="u => !u.Enable" | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black">    @(context.Description(x => x.IntervalTime))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.IntervalTime /> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.RpcWriteEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.RpcWriteEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black">    @(context.Description(x => x.Unit))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.Unit /> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black">    @(context.Description(x => x.VariableAddress))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.VariableAddress /> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black">    @(context.Description(x => x.ProtectTypeEnum))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value="context.ProtectTypeEnum" Outlined | ||||
|                                              Items=@(typeof(ProtectTypeEnum).GetEnumList()) | ||||
|                                              MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.des) | ||||
|                                              ItemValue=@(u =>(ProtectTypeEnum)u.value) | ||||
|                                              HideDetails=@("auto") Height="30" | ||||
|                                                   Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black">    @(context.Description(x => x.DataTypeEnum))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value="context.DataTypeEnum" Outlined | ||||
|                                              Items=@(typeof(DataTypeEnum).GetEnumList()) Clearable | ||||
|                                              MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.des) | ||||
|                                              ItemValue=@(u =>(DataTypeEnum)u.value) | ||||
|                                              HideDetails=@("auto") Height="30" | ||||
|                                                   Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|  | ||||
|                                 <MSubheader Class="font-weight-black">    @(context.Description(x => x.ReadExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.ReadExpressions /> | ||||
|                                 </MCol> | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|  | ||||
|                                     <MSubheader Class="font-weight-black">    @(context.Description(x => x.WriteExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.WriteExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|                                 @if (OtherMethods.ContainsKey(context.DeviceId)) | ||||
|                             { | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black">    @(context.Description(x => x.OtherMethod))  </MSubheader> | ||||
|                                     <MSelect Class="mr-3" @bind-Value="context.OtherMethod" Outlined | ||||
|                                                  Items=@(OtherMethods[context.DeviceId]) Clearable | ||||
|                                                  MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })" | ||||
|                                                  ItemText=@((u) =>u) | ||||
|                                                  ItemValue=@(u =>u) | ||||
|                                                  HideDetails=@("auto") Height="30" | ||||
|                                                       Dense> | ||||
|                                     </MSelect> | ||||
|                                 </MCol> | ||||
|  | ||||
|                             } | ||||
|  | ||||
|                         </MRow> | ||||
|  | ||||
|                     </MCard> | ||||
|                 } | ||||
|             </MTabItem> | ||||
|  | ||||
|             <MTabItem Value="2"> | ||||
|                 @if (tab == 2) | ||||
|                 { | ||||
|                     <MCard Flat Class="ma-2"> | ||||
|                             <MRow NoGutters Class="my-2" Justify="JustifyTypes.Center"> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1 py-2"> | ||||
|                                     <MDivider Center Height="2"></MDivider> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseAlarmEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.BoolCloseAlarmEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseAlarmText))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolCloseAlarmText /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolCloseRestrainExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolCloseRestrainExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1 py-2"> | ||||
|                                     <MDivider Center Height="2"></MDivider> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenAlarmEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.BoolOpenAlarmEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenAlarmText))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolOpenAlarmText /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.BoolOpenRestrainExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.BoolOpenRestrainExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1 py-2"> | ||||
|                                     <MDivider Center Height="2"></MDivider> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.HHAlarmEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmText))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHAlarmText /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HHAlarmCode))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHAlarmCode /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HHRestrainExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HHRestrainExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1 py-2"> | ||||
|                                     <MDivider Center Height="2"></MDivider> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.HAlarmEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmText))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HAlarmText /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HAlarmCode))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HAlarmCode /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1 py-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HRestrainExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.HRestrainExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1 py-2"> | ||||
|                                     <MDivider Center Height="2"></MDivider> | ||||
|                                 </MCol> | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.LAlarmEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmText))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LAlarmText /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.LAlarmCode))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LAlarmCode /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.LRestrainExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LRestrainExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=12 Cols=12 class="px-1 py-2"> | ||||
|                                     <MDivider Center Height="2"></MDivider> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.LLAlarmEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmText))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLAlarmText /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.LLAlarmCode))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLAlarmCode /> | ||||
|                                 </MCol> | ||||
|  | ||||
|                                 <MCol Md=6 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.LLRestrainExpressions))  </MSubheader> | ||||
|                                 <MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.LLRestrainExpressions /> | ||||
|                                 </MCol> | ||||
|  | ||||
|  | ||||
|                             </MRow> | ||||
|  | ||||
|                         </MCard> | ||||
|                 } | ||||
|             </MTabItem> | ||||
|  | ||||
|  | ||||
|             <MTabItem Value="3"> | ||||
|                 @if (tab == 3) | ||||
|                 { | ||||
|                     <MCard Flat Class="ma-2"> | ||||
|  | ||||
|                             <MRow NoGutters Class="my-2" Justify="JustifyTypes.Center"> | ||||
|                                 <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                     <MSubheader Class="font-weight-black"> @(context.Description(x => x.HisEnable))  </MSubheader> | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.HisEnable Outlined | ||||
|                                              Items=@(new List<bool>(){true,false}) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.ToString()) ItemValue=@(u =>u) | ||||
|                                              HideDetails=@("auto") Height="30" Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                             <MCol Md=12 Cols=12 class="px-1"> | ||||
|                                 <MSubheader Class="font-weight-black"> @(context.Description(x => x.HisType))  </MSubheader> | ||||
|  | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@context.HisType Outlined | ||||
|                                              Items=@(typeof(HisType).GetEnumList()) Clearable | ||||
|                                              MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                              ItemText=@((u) =>u.des) | ||||
|                                              ItemValue=@(u =>(HisType)u.value) | ||||
|                                              HideDetails=@("auto") Height="30" | ||||
|                                                   Dense> | ||||
|                                 </MSelect> | ||||
|                             </MCol> | ||||
|  | ||||
|                         </MRow> | ||||
|  | ||||
|                     </MCard> | ||||
|                 } | ||||
|             </MTabItem> | ||||
|             <MTabItem Value="4"> | ||||
|                 @if (tab == 4) | ||||
|                 { | ||||
|                     <MCard Flat Class="pa-2"> | ||||
|                             <MRow Class="px-1 py-6" Align="AlignTypes.Center"> | ||||
|  | ||||
|                                 <MSelect Class="mr-3" @bind-Value=@choiceUploadDeviceId | ||||
|                                               Outlined | ||||
|                                          Items=@(UploadDevices) | ||||
|                                               Clearable | ||||
|                                          MenuProps="@(props => { props.Bottom = true; props.OffsetY = true; })" | ||||
|                                          ItemText=@((u) =>u.Name) ItemValue=@(u =>u.Id) | ||||
|                                          HideDetails=@("auto") Height="30" | ||||
|                                               Dense> | ||||
|                             </MSelect> | ||||
|                             <MButton Class="my-3" OnClick=@(async() => | ||||
|                              { | ||||
|                              if(choiceUploadDeviceId>0) | ||||
|                              { | ||||
|                              var data=GetDriverProperties(UploadDevices.FirstOrDefault(a=>a.Id==choiceUploadDeviceId).PluginId,context.VariablePropertys.ContainsKey(choiceUploadDeviceId)?context.VariablePropertys[choiceUploadDeviceId]:null); | ||||
|                              if(data?.Count>0) | ||||
|                              { | ||||
|                              context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId,a=> data,(a,b)=>data); | ||||
|                              } | ||||
|                              else | ||||
|                              { | ||||
|                              await PopupService.EnqueueSnackbarAsync("此上传设备没有变量上传属性",AlertTypes.Warning); | ||||
|                              context.VariablePropertys.Remove(choiceUploadDeviceId); | ||||
|                              } | ||||
|                              } | ||||
|                              else | ||||
|                              { | ||||
|                              await PopupService.EnqueueSnackbarAsync("需选择上传设备",AlertTypes.Warning); | ||||
|                              } | ||||
|                              } | ||||
|                              ) Color="primary"> | ||||
|                                 添加/刷新属性 | ||||
|                             </MButton> | ||||
|  | ||||
|                         </MRow> | ||||
|                         @if (context.VariablePropertys != null) | ||||
|                         { | ||||
|                             @foreach (var item in context.VariablePropertys) | ||||
|                             { | ||||
|                                 <MCard Class="pa-2 my-3"> | ||||
|  | ||||
|                                     <MSubheader Class="mt-4 font-weight-black"> | ||||
|                                         @( | ||||
|                                             UploadDevices.FirstOrDefault(a => a.Id == item.Key)?.Name ?? "未知" | ||||
|                                                 ) | ||||
|                                     </MSubheader> | ||||
|                                     @foreach (var property in item.Value ?? new()) | ||||
|                                     { | ||||
|                                         <MSubheader Class="mt-4 font-weight-black"> @property.Description </MSubheader> | ||||
|                                         <MTooltip Disabled=string.IsNullOrEmpty(property.Remark) | ||||
|                                                             Bottom | ||||
|                                                       Context="tip"> | ||||
|                                                 <ActivatorContent> | ||||
|                                                     <MTextField @attributes="@tip.Attrs" | ||||
|                                                                             Dense | ||||
|                                                                             Outlined | ||||
|                                                                 HideDetails="@("auto")" @bind-Value=@property.Value /> | ||||
|                                                 </ActivatorContent> | ||||
|                                                 <ChildContent> | ||||
|                                                     <span>@property.Remark</span> | ||||
|                                                 </ChildContent> | ||||
|                                             </MTooltip> | ||||
|                                     } | ||||
|                                     <MCardActions> | ||||
|                                         <MButton Class="my-3" OnClick=@(() => | ||||
|                                  { | ||||
|                                  context.VariablePropertys.Remove(item.Key); | ||||
|                                  } | ||||
|                                  ) Color="primary"> | ||||
|                                             删除 | ||||
|                                         </MButton> | ||||
|                                     </MCardActions> | ||||
|                                 </MCard> | ||||
|                             } | ||||
|  | ||||
|  | ||||
|                         } | ||||
|                     </MCard> | ||||
|                 } | ||||
|             </MTabItem> | ||||
|         </MTabsItems> | ||||
|  | ||||
|     </div> | ||||
|     ; | ||||
|         return renderFragment; | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user