mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-25 12:43:09 +08:00
Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7590ba4ad5 | ||
|
|
39ce18f8bb | ||
|
|
777a4f9d3f | ||
|
|
b91a938787 | ||
|
|
c903abdb1b | ||
|
|
3279bc0580 | ||
|
|
bd32c9f49e | ||
|
|
5507088e3d | ||
|
|
37ad48ae4a | ||
|
|
120c6e6d87 | ||
|
|
866e907b7e | ||
|
|
2bd8c2cb10 | ||
|
|
c6c9919178 | ||
|
|
575bf9d1e0 | ||
|
|
82a56e0285 | ||
|
|
25d5f7c132 | ||
|
|
a3e9ecf30f | ||
|
|
ec5ff0a07f | ||
|
|
be836d30c5 | ||
|
|
42b1529a5f | ||
|
|
47708c4807 | ||
|
|
d2a51e004c | ||
|
|
ab14230101 | ||
|
|
4bcf8c1f78 | ||
|
|
44d00e9da3 | ||
|
|
d7e6a4493c | ||
|
|
53e89d8c54 | ||
|
|
4497c13634 | ||
|
|
4a35fade46 | ||
|
|
6cba0601fd | ||
|
|
ed4332ea78 | ||
|
|
aba069cec5 | ||
|
|
8a82ac0a11 | ||
|
|
1cd1456d75 | ||
|
|
b791a3eb10 | ||
|
|
3b22a8b170 | ||
|
|
419e8214ca | ||
|
|
b9f8571f0f | ||
|
|
c6d9a9d7f8 | ||
|
|
310aba6ccb | ||
|
|
9bd89ac4f6 | ||
|
|
ea6a51dca9 | ||
|
|
e701bcc50c | ||
|
|
a1abf06e75 | ||
|
|
76ace394b0 | ||
|
|
4dac462f8f | ||
|
|
039672b1e7 | ||
|
|
1b26ecbbf5 | ||
|
|
f4a7e96943 | ||
|
|
a45bc2954f | ||
|
|
62f32467b7 | ||
|
|
600a1bf201 | ||
|
|
7e196e7aa6 | ||
|
|
9ad3507b66 | ||
|
|
add1bdfcf6 | ||
|
|
7ea8a7c079 | ||
|
|
28e31f5165 | ||
|
|
10a6975c5d | ||
|
|
b34ea87660 | ||
|
|
86ed69c50b | ||
|
|
02e824154c | ||
|
|
dc973c8491 | ||
|
|
0a28e3a8d3 | ||
|
|
7a48c260e1 | ||
|
|
c55c49a3a2 | ||
|
|
0fc9b06d12 | ||
|
|
d71ad5a6bf | ||
|
|
f0b3028306 | ||
|
|
752992c527 | ||
|
|
43c4476396 | ||
|
|
f8172bed56 | ||
|
|
c1f71b4cfc | ||
|
|
4799da15e7 | ||
|
|
4936e47c7a | ||
|
|
cb965373aa | ||
|
|
654a91184a | ||
|
|
57997f7f4b | ||
|
|
ca2f5be3d1 | ||
|
|
1b091073f1 | ||
|
|
85d2d3c442 | ||
|
|
d77dfd63cf | ||
|
|
db2bf52fca | ||
|
|
2c62d97440 | ||
|
|
53b2e64214 | ||
|
|
ccf82a3ee5 | ||
|
|
9cb6f35e60 | ||
|
|
da2e33a040 | ||
|
|
f2d260bfa4 | ||
|
|
783c7244f9 | ||
|
|
c9287e0e63 | ||
|
|
69b84d115c | ||
|
|
1745b898b1 | ||
|
|
5ba4c85249 | ||
|
|
03c87bb46d | ||
|
|
dff5a76e9e | ||
|
|
50386f4ca4 | ||
|
|
de6ba2ec80 | ||
|
|
ce126015d9 | ||
|
|
f2337c3d43 | ||
|
|
41b59d322a | ||
|
|
dfea7c111e | ||
|
|
3d5d672297 | ||
|
|
e4dcc6f342 | ||
|
|
9847249611 | ||
|
|
19742a54ec | ||
|
|
5af9ae7a80 | ||
|
|
78962a4a75 | ||
|
|
9a35d826aa | ||
|
|
c9cdc89d35 | ||
|
|
33b75703ba | ||
|
|
ca55aa3a24 | ||
|
|
95307c3902 | ||
|
|
89c8eae7e2 | ||
|
|
91614772dc | ||
|
|
cd497ab4dc | ||
|
|
f81895905e | ||
|
|
d82fafaa7a | ||
|
|
0ea01acc46 | ||
|
|
df55792a68 | ||
|
|
6a19c45269 | ||
|
|
23ee345441 | ||
|
|
0e8ff253f3 | ||
|
|
89bddb0bc4 | ||
|
|
77bbca9bb3 | ||
|
|
1acd4d8d58 | ||
|
|
4db72a712d | ||
|
|
da2b7ba08e | ||
|
|
f8b49411bf | ||
|
|
a04b0d99b0 | ||
|
|
9e9735f617 | ||
|
|
f906b36f01 | ||
|
|
a9d1f4f854 | ||
|
|
d017ae905f | ||
|
|
bede280507 | ||
|
|
ea130047bc | ||
|
|
d77d8de63e | ||
|
|
f5413d9202 | ||
|
|
559fc8f216 | ||
|
|
34b2df911d | ||
|
|
3d5a8a8cbd | ||
|
|
2b07097e14 | ||
|
|
372e943d8b | ||
|
|
95afcedc4f | ||
|
|
c1dc8f017c | ||
|
|
dbeddca263 | ||
|
|
e65d133dec | ||
|
|
b5e1dea1c2 | ||
|
|
3f19469939 | ||
|
|
87874b037a | ||
|
|
adda6841d1 | ||
|
|
a8092b921b | ||
|
|
930e4d69d1 | ||
|
|
d9219df45b | ||
|
|
a6d4ec28db | ||
|
|
b29e13f777 | ||
|
|
9363b78ff6 | ||
|
|
eba411fec7 | ||
|
|
9afe99e134 | ||
|
|
6678856178 | ||
|
|
a1fea38c2c | ||
|
|
2d3265e66d | ||
|
|
f6d4400db9 | ||
|
|
6d892a9266 | ||
|
|
356fa08da5 | ||
|
|
2225f8c890 | ||
|
|
d1a89739f9 | ||
|
|
6ec6896763 | ||
|
|
9fb718d610 | ||
|
|
c913b89a47 | ||
|
|
58b45693b7 | ||
|
|
29d3ece9b1 | ||
|
|
7e5db75230 | ||
|
|
283c0b9845 | ||
|
|
071a0f1651 | ||
|
|
1a116557a4 | ||
|
|
1468297626 | ||
|
|
87e94fdf52 | ||
|
|
64e15c33c8 | ||
|
|
7170f435d1 | ||
|
|
9942be0493 | ||
|
|
eaad36277f | ||
|
|
37cc498568 |
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "Admin"]
|
||||
url = https://gitee.com/ThingsGateway/BlazorAdmin
|
||||
path = Admin
|
||||
|
||||
1
Admin
Submodule
1
Admin
Submodule
Submodule Admin added at 3b73b7283a
@@ -10,6 +10,42 @@
|
||||
|
||||
[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
|
||||
|
||||
## 源码获取/更新
|
||||
|
||||
### 源码克隆
|
||||
|
||||
|
||||
注意因仓库包含子模块,直接下载zip包会导致子模块丢失,建议使用git clone命令
|
||||
|
||||
``` shell
|
||||
|
||||
https://gitee.com/ThingsGateway/ThingsGateway.git
|
||||
|
||||
```
|
||||
|
||||
### 源码更新
|
||||
|
||||
在vs中打开powerShell窗口,执行以下命令,或根目录下的`git_pull.bat`脚本
|
||||
|
||||
<img src="https://foruda.gitee.com/images/1736150639726525137/8ff84c29_7941935.png" width="400px" />
|
||||
|
||||
|
||||
``` shell
|
||||
|
||||
chcp 65001
|
||||
|
||||
rem 更新主仓库
|
||||
git pull
|
||||
|
||||
rem 初始化并更新所有子模块
|
||||
git submodule update --init
|
||||
|
||||
pause
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 插件列表
|
||||
|
||||
#### 采集插件
|
||||
|
||||
9
git_pull.bat
Normal file
9
git_pull.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
chcp 65001
|
||||
|
||||
rem 更新主仓库
|
||||
git pull
|
||||
|
||||
rem 初始化并更新所有子模块
|
||||
git submodule update --init
|
||||
|
||||
pause
|
||||
@@ -1,248 +1,162 @@
|
||||
root = true
|
||||
|
||||
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
|
||||
# All files
|
||||
[*]
|
||||
indent_style = space
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
spelling_exclusion_path = .\exclusion.dic
|
||||
|
||||
# Microsoft .NET properties
|
||||
csharp_new_line_before_members_in_object_initializers = false
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True
|
||||
dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field
|
||||
dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef
|
||||
dotnet_naming_rule.unity_serialized_field_rule.severity = warning
|
||||
dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style
|
||||
dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols
|
||||
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
# ReSharper properties
|
||||
resharper_autodetect_indent_settings = true
|
||||
resharper_formatter_off_tag = @formatter:off
|
||||
resharper_formatter_on_tag = @formatter:on
|
||||
resharper_formatter_tags_enabled = true
|
||||
resharper_new_line_before_while = true
|
||||
resharper_place_attribute_on_same_line = false
|
||||
resharper_show_autodetect_configure_formatting_tip = false
|
||||
resharper_use_indent_from_vs = false
|
||||
|
||||
# ReSharper inspection severities
|
||||
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||
resharper_arrange_this_qualifier_highlighting = hint
|
||||
resharper_arrange_type_member_modifiers_highlighting = hint
|
||||
resharper_arrange_type_modifiers_highlighting = hint
|
||||
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
|
||||
resharper_built_in_type_reference_style_highlighting = hint
|
||||
resharper_redundant_base_qualifier_highlighting = warning
|
||||
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
||||
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
||||
resharper_web_config_module_not_resolved_highlighting = warning
|
||||
resharper_web_config_type_not_resolved_highlighting = warning
|
||||
resharper_web_config_wrong_module_highlighting = warning
|
||||
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
indent_size = 4
|
||||
|
||||
[*.{cs,css,js,json,*html,razor,txt,log}]
|
||||
charset = utf-8-bom
|
||||
|
||||
[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml config files
|
||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.{ps1,psm1}]
|
||||
indent_size = 4
|
||||
|
||||
[*.sh]
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
|
||||
###############################
|
||||
# .NET Coding Conventions #
|
||||
###############################
|
||||
[*.{cs,vb}]
|
||||
# Organize usings
|
||||
dotnet_sort_system_directives_first = false
|
||||
# this. preferences
|
||||
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
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
# Style Definitions
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
# Use PascalCase for constant fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_prefer_collection_expression = when_types_exactly_match:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||
dotnet_code_quality_unused_parameters = all:suggestion
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
[*.cs]
|
||||
# var preferences
|
||||
csharp_style_var_for_built_in_types = true:silent
|
||||
csharp_style_var_when_type_is_apparent = true:silent
|
||||
csharp_style_var_elsewhere = true:silent
|
||||
csharp_prefer_static_local_function = true:silent
|
||||
# Expression-bodied members
|
||||
#### 命名样式 ####
|
||||
|
||||
# 命名规则
|
||||
|
||||
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
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
# Null-checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
# Modifier preferences
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
|
||||
# Expression-level preferences
|
||||
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_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
###############################
|
||||
# C# Formatting Rules #
|
||||
###############################
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
# Indentation preferences
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
###############################
|
||||
# VB Coding Conventions #
|
||||
###############################
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
|
||||
[*.vb]
|
||||
# Modifier preferences
|
||||
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
|
||||
#### 命名样式 ####
|
||||
|
||||
# 命名规则
|
||||
|
||||
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]
|
||||
# Add file header
|
||||
file_header_template = ------------------------------------------------------------------------------\n此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充\n此代码版权(除特别声明外的代码)归作者本人Diego所有\n源代码使用协议遵循本仓库的开源协议及附加协议\nGitee源代码仓库:https://gitee.com/diego2098/ThingsGateway\nGithub源代码仓库:https://github.com/kimdiego2098/ThingsGateway\n使用文档:https://thingsgateway.cn/\nQQ群:605534569\n------------------------------------------------------------------------------
|
||||
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
csharp_style_expression_bodied_local_functions = true:silent
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_prefer_readonly_struct_member = true:suggestion
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:silent
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
[*.{cs,vb}]
|
||||
end_of_line = crlf
|
||||
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
|
||||
|
||||
|
||||
dotnet_diagnostic.CA2208.severity = none
|
||||
dotnet_diagnostic.CA2008.severity = none
|
||||
dotnet_diagnostic.CA1812.severity = none
|
||||
dotnet_diagnostic.CA1508.severity = none
|
||||
dotnet_diagnostic.CA1512.severity = none
|
||||
dotnet_diagnostic.CA1513.severity = none
|
||||
dotnet_diagnostic.CA1810.severity = none
|
||||
dotnet_diagnostic.CA1814.severity = none
|
||||
dotnet_diagnostic.CA1815.severity = none
|
||||
dotnet_diagnostic.CA1835.severity = none
|
||||
dotnet_diagnostic.CA1819.severity = none
|
||||
dotnet_diagnostic.CA1823.severity = none
|
||||
dotnet_diagnostic.CA2002.severity = none
|
||||
dotnet_diagnostic.CA5350.severity = none
|
||||
dotnet_diagnostic.CA5351.severity = none
|
||||
dotnet_diagnostic.CA5358.severity = none
|
||||
dotnet_diagnostic.CA5384.severity = none
|
||||
dotnet_diagnostic.CA5392.severity = none
|
||||
dotnet_diagnostic.CA1805.severity = none
|
||||
dotnet_diagnostic.CA1851.severity = none
|
||||
dotnet_diagnostic.CA1510.severity = none
|
||||
dotnet_diagnostic.CA5401.severity = none
|
||||
dotnet_diagnostic.CA2022.severity = none
|
||||
dotnet_diagnostic.CA1848.severity = none
|
||||
dotnet_diagnostic.CA2000.severity = none
|
||||
dotnet_diagnostic.CA5394.severity = none
|
||||
dotnet_diagnostic.CA3003.severity = none
|
||||
dotnet_diagnostic.CA1515.severity = none
|
||||
dotnet_diagnostic.CA1849.severity = none
|
||||
|
||||
dotnet_code_quality.CA1822.api_surface = private, internal
|
||||
@@ -1,7 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"$schema": "null",
|
||||
|
||||
"AllowedHosts": "*",
|
||||
|
||||
"AppSettings": {
|
||||
"InjectSpecificationDocument": true, // 生产环境是否开启Swagger
|
||||
"ExternalAssemblies": [ "Plugins" ], // 插件目录
|
||||
|
||||
// nuget动态加载的程序集
|
||||
"SupportPackageNamePrefixs": [
|
||||
|
||||
"ThingsGateway.Foundation.Razor",
|
||||
"ThingsGateway.Debug.Razor",
|
||||
"ThingsGateway.Core",
|
||||
"ThingsGateway.Razor"
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
"DynamicApiControllerSettings": {
|
||||
//"DefaultRoutePrefix": "api", // 默认路由前缀
|
||||
"CamelCaseSeparator": "", // 驼峰命名分隔符
|
||||
"SplitCamelCase": false, // 切割骆驼(驼峰)/帕斯卡命名
|
||||
"LowercaseRoute": false, // 小写路由格式
|
||||
"AsLowerCamelCase": true, // 小驼峰命名(首字母小写)
|
||||
"KeepVerb": false, // 保留动作方法请求谓词
|
||||
"KeepName": false // 保持原有名称不处理
|
||||
},
|
||||
"FriendlyExceptionSettings": {
|
||||
"DefaultErrorMessage": "系统异常,请联系管理员",
|
||||
"ThrowBah": true, // 是否将 Oops.Oh 默认抛出为业务异常
|
||||
"LogError": false // 是否输出异常日志
|
||||
},
|
||||
"CorsAccessorSettings": {
|
||||
"PolicyName": "ThingsGateway",
|
||||
"WithExposedHeaders": [ "Content-Disposition", "X-Pagination", "access-token", "x-access-token" ], // 如果前端不代理且是axios请求
|
||||
"SignalRSupport": true // 启用 SignalR 跨域支持
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
|
||||
|
||||
//BootstrapBlazor配置
|
||||
"BootstrapBlazorOptions": {
|
||||
|
||||
|
||||
"ToastDelay": 4000,
|
||||
"MessageDelay": 4000,
|
||||
"SwalDelay": 4000,
|
||||
"EnableErrorLogger": true,
|
||||
"FallbackCulture": "zh-CN",
|
||||
"SupportedCultures": [
|
||||
"zh-CN",
|
||||
"en-US",
|
||||
"zh-TW"
|
||||
],
|
||||
"DefaultCultureInfo": "zh-CN", //修改默认语言
|
||||
"TableSettings": {
|
||||
"CheckboxColumnWidth": 36
|
||||
},
|
||||
"IgnoreLocalizerMissing": true,
|
||||
"StepSettings": {
|
||||
"Short": 1,
|
||||
"Int": 1,
|
||||
"Long": 1,
|
||||
"Float": 0.1,
|
||||
"Double": 0.01,
|
||||
"Decimal": 0.01
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
|
||||
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
},
|
||||
"EventLog": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
} //windows事件输出日志等级
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
} //控制台输出日志等级
|
||||
},
|
||||
"BackendLog": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Monitor": {
|
||||
"GlobalEnabled": false, // 启用全局拦截日志
|
||||
"IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效
|
||||
"ExcludeOfMethods": [], // 排除特定方法,当GlobalEnabled=true有效
|
||||
"BahLogLevel": "Information", // Oops.Oh 和 Oops.Bah 业务日志输出级别
|
||||
"WithReturnValue": true, // 是否包含返回值,默认true
|
||||
"ReturnValueThreshold": 500, // 返回值字符串阈值,默认0全量输出
|
||||
"JsonBehavior": "None", // 是否输出Json,默认None(OnlyJson、All)
|
||||
"JsonIndented": false, // 是否格式化Json
|
||||
"UseUtcTimestamp": false // 时间格式UTC、LOCAL
|
||||
},
|
||||
|
||||
|
||||
//日志配置
|
||||
"LogJob": {
|
||||
"DaysAgo": 10 //清理10天前日志
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"Menu": {
|
||||
|
||||
"MenuItems": [
|
||||
{
|
||||
"Url": "/",
|
||||
"Text": "首页"
|
||||
},
|
||||
{
|
||||
"Text": "Modbus",
|
||||
"Items": [
|
||||
{
|
||||
"Url": "/ModbusMaster",
|
||||
"Text": "ModbusMaster"
|
||||
},
|
||||
{
|
||||
"Url": "/ModbusSlave",
|
||||
"Text": "ModbusSlave"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Url": "/SiemensS7Master",
|
||||
"Text": "Siemens"
|
||||
},
|
||||
{
|
||||
"Url": "/Dlt645_2007Master",
|
||||
"Text": "Dlt645_2007Master"
|
||||
},
|
||||
{
|
||||
"Url": "/OpcUaMaster",
|
||||
"Text": "OpcUaMaster"
|
||||
},
|
||||
{
|
||||
"Url": "/OpcDaMaster",
|
||||
"Text": "OpcDaMaster"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
//网站配置
|
||||
"Website": {
|
||||
"Copyright": "版权所有 © 2023-present Diego",
|
||||
"IsShowAbout": true, //是否显示关于页面
|
||||
"SourceUrl": "https://gitee.com/diego2098/ThingsGateway",
|
||||
"WikiUrl": "https://thingsgateway.cn/",
|
||||
"QQGroup1Link": "http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569",
|
||||
"QQGroup1Number": "605534569",
|
||||
"Title": "ThingsGateway",
|
||||
"Demo": false
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Debug.MainLayout": {
|
||||
"FullScreenButton": "Full Screen",
|
||||
"About": "About"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ThingsGateway.Debug.MainLayout": {
|
||||
"FullScreenButton": "全屏",
|
||||
"About": "关于"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ThingsGateway.Debug.MainLayout": {
|
||||
"FullScreenButton": "全屏",
|
||||
"About": "關於"
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout BaseLayout
|
||||
@namespace ThingsGateway.Debug
|
||||
@using BootstrapBlazor.Components
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.NewLife.Extension
|
||||
@using ThingsGateway.Razor
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div class="mainlayout">
|
||||
|
||||
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
|
||||
ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuService.MenuItems"
|
||||
AllowDragTab=true AdditionalAssemblies="@_assemblyList"
|
||||
UseTabSet="false" TabDefaultUrl="/">
|
||||
<Header>
|
||||
|
||||
<div class="flex-fill"></div>
|
||||
@* 搜索框 *@
|
||||
<GlobalSearch Menus=@(MenuService.SameLevelMenuItems) />
|
||||
@* 语言选择 *@
|
||||
|
||||
<div class="d-none d-xl-flex ">
|
||||
<CultureChooser />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
@* 全屏按钮 *@
|
||||
<FullScreenButton class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-arrows-alt"
|
||||
TooltipPlacement=Placement.Bottom TooltipText="@Localizer[nameof(FullScreenButton)]" />
|
||||
@if (WebsiteOption.Value.IsShowAbout)
|
||||
{
|
||||
<Button OnClick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
|
||||
}
|
||||
@* 版本号 *@
|
||||
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
|
||||
|
||||
@* 主题切换 *@
|
||||
@* <ThemeToggle /> *@
|
||||
<ThemeProvider class="layout-header-bar d-none d-lg-flex px-0"></ThemeProvider>
|
||||
|
||||
</Header>
|
||||
<Side>
|
||||
<div class="layout-banner">
|
||||
<span class="avatar">
|
||||
@WebsiteOption.Value.Title?.GetNameLen2()
|
||||
</span>
|
||||
|
||||
<div class="layout-title d-flex align-items-center justify-content-center">
|
||||
<span>@WebsiteOption.Value.Title</span>
|
||||
</div>
|
||||
</div>
|
||||
</Side>
|
||||
<Main>
|
||||
|
||||
<Tab ClickTabToNavigation="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
|
||||
Menus="@MenuService.MenuItems" AdditionalAssemblies="@_assemblyList"
|
||||
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
|
||||
{
|
||||
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
|
||||
})>
|
||||
</Tab>
|
||||
|
||||
</Main>
|
||||
<NotAuthorized>
|
||||
<Redirect />
|
||||
</NotAuthorized>
|
||||
</Layout>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class MainLayout
|
||||
{
|
||||
private List<Assembly> _assemblyList = new();
|
||||
|
||||
private string _versionString = string.Empty;
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private DialogService? DialogService { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IStringLocalizer<MainLayout>? Localizer { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IMenuService? MenuService { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IAppVersionService? VersionService { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_assemblyList = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a =>
|
||||
a.GetTypes()).Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass
|
||||
&& u.IsDefined(typeof(Microsoft.AspNetCore.Components.RouteAttribute), true)).Select(a => a.Assembly)
|
||||
//.Where(a => a != typeof(BlazorApp).Assembly)
|
||||
.Distinct().ToList();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
_versionString = $"v{VersionService.Version}";
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task ShowAbout()
|
||||
{
|
||||
DialogOption? op = null;
|
||||
|
||||
op = new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
Size = Size.Medium,
|
||||
ShowFooter = false,
|
||||
Title = Localizer["About"],
|
||||
BodyTemplate = BootstrapDynamicComponent.CreateComponent<About>().Render(),
|
||||
};
|
||||
await DialogService.Show(op);
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
::deep .avatar {
|
||||
border-radius: 1.5rem;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background-color: var(--bs-green);
|
||||
color: #fff;
|
||||
flex: 0 0 auto;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .menu-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout-main > .tabs > .tabs-body {
|
||||
background-color: var(--tabs-body-bg);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout-main > .tabs > .tabs-body > .tabs-body-content {
|
||||
height: var(--bb-layout-body-height);
|
||||
background-color: var(--bs-body-bg);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs {
|
||||
--bb-tabs-item-height: 32px;
|
||||
--bb-tabs-body-padding: 0.5rem;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs.tabs-border-card {
|
||||
box-shadow: 0 0px 0px 0 rgba(0,0,0,0),0 0 6px 0 rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs .extend .nav-link-bar.left {
|
||||
border-width: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs-nav-wrap > .nav-link-bar.dropdown {
|
||||
border-width: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs .extend .nav-link-bar.right {
|
||||
border-width: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs .tabs-item-fix {
|
||||
border-width: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs.tabs-card > .tabs-header .tabs-item {
|
||||
border-width: 0px 0px 0px 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs.tabs-border-card > .tabs-header .tabs-item {
|
||||
border-width: 0px 0px 0px 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs.tabs-card .tabs-header .tabs-item.active {
|
||||
border-width: 0px 0px 1px 0px;
|
||||
border-style: solid;
|
||||
border-color: var(--bb-tabs-item-active-color);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs.tabs-border-card .tabs-header .tabs-item.active {
|
||||
border-width: 0px 0px 1px 0px;
|
||||
border-style: solid;
|
||||
border-color: var(--bb-tabs-item-active-color);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs.tabs-card .tabs-header .tabs-item.active {
|
||||
background-color: var(--bs-primary-bg1);
|
||||
}
|
||||
|
||||
.mainlayout ::deep.tabs.tabs-card .tabs-header .tabs-item:hover {
|
||||
background-color: var(--bs-primary-bg1);
|
||||
}
|
||||
|
||||
.mainlayout ::deep.tabs.tabs-border-card .tabs-header .tabs-item:hover {
|
||||
background-color: var(--bs-primary-bg1);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs-nav-wrap .nav-link-bar {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .tabs-item .tabs-item-close {
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .table-wrapper {
|
||||
border-radius: unset;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout-side {
|
||||
box-shadow: inset -1px 0 0px 0px var(--bs-border-color); /* 下内阴影 */
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout-banner {
|
||||
box-shadow: inset -1px 0 0px 0px var(--bs-border-color); /* 下内阴影 */
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout {
|
||||
--bb-layout-header-height: 44px;
|
||||
--bb-layout-headerbar-background: transparent;
|
||||
--bs-navbar-color: var(--bb-layout-header-color);
|
||||
--bs-navbar-hover-color: var(--bs-primary);
|
||||
--bb-layout-header-background: var(--tg-nav-bg);
|
||||
--bb-layout-sidebar-background: var(--tg-nav-bg);
|
||||
--bb-layout-footer-background: var(--tg-nav-bg);
|
||||
--bb-layout-sidebar-banner-background: var(--tg-nav-bg);
|
||||
--bb-layout-banner-font-size: 1.2rem;
|
||||
--bb-layout-banner-logo-width: 36px;
|
||||
--bb-layout-banner-logo-height: 36px;
|
||||
--line-chart-height: 350px;
|
||||
--bb-layout-body-height: calc(100vh - var(--bs-header-height) - var(--bb-layout-header-height) - 20px);
|
||||
--line-chart-table-height: calc(100vh - var(--bs-header-height) - var(--bb-layout-header-height) - var(--line-chart-height) - 30px);
|
||||
--table-height: calc(100vh - var(--bs-header-height) - var(--bb-layout-header-height) - 30px);
|
||||
--bs-header-height: 30px;
|
||||
}
|
||||
|
||||
.mainlayout ::deep .dropdown-logout {
|
||||
--bb-logout-avatar-width: 32px;
|
||||
--bb-logout-avatar-height: 32px;
|
||||
--bb-logout-user-bg: rgba(52,58,64,0.7);
|
||||
--bb-logout-menu-border-color: var(--bs-border-color);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout-header-bar {
|
||||
border-color: transparent;
|
||||
border: 0px;
|
||||
color: var(--bb-layout-header-color);
|
||||
}
|
||||
|
||||
.mainlayout ::deep .layout-header-bar:hover {
|
||||
color: var(--bs-navbar-hover-color);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
using Photino.Blazor;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife.Log;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
|
||||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
//当前工作目录设为程序集的基目录
|
||||
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
|
||||
// 增加中文编码支持
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
#region 控制台输出Logo
|
||||
|
||||
Console.Write(Environment.NewLine);
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
XTrace.WriteLine(string.Empty);
|
||||
Console.WriteLine(
|
||||
"""
|
||||
|
||||
_______ _ _ _____ _
|
||||
|__ __|| | (_) / ____| | |
|
||||
| | | |__ _ _ __ __ _ ___ | | __ __ _ | |_ ___ __ __ __ _ _ _
|
||||
| | | '_ \ | || '_ \ / _` |/ __|| | |_ | / _` || __|/ _ \\ \ /\ / // _` || | | |
|
||||
| | | | | || || | | || (_| |\__ \| |__| || (_| || |_| __/ \ V V /| (_| || |_| |
|
||||
|_| |_| |_||_||_| |_| \__, ||___/ \_____| \__,_| \__|\___| \_/\_/ \__,_| \__, |
|
||||
__/ | __/ |
|
||||
|___/ |___/
|
||||
|
||||
"""
|
||||
);
|
||||
Console.ResetColor();
|
||||
|
||||
#endregion 控制台输出Logo
|
||||
|
||||
var builder = PhotinoBlazorAppBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<Routes>("#app");
|
||||
|
||||
var options = GenericRunOptions.DefaultSilence
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
foreach (var item in builder.Services)
|
||||
{
|
||||
services.Add(item);
|
||||
}
|
||||
});
|
||||
;
|
||||
Serve.BuildApplication(options, out var app);
|
||||
|
||||
app.Start();
|
||||
|
||||
var hybridApp = builder.Build(app.Services);
|
||||
|
||||
hybridApp.MainWindow.ContextMenuEnabled = false;
|
||||
hybridApp.MainWindow.DevToolsEnabled = true;
|
||||
hybridApp.MainWindow.GrantBrowserPermissions = true;
|
||||
hybridApp.MainWindow.SetUseOsDefaultLocation(false);
|
||||
hybridApp.MainWindow.SetUseOsDefaultSize(false);
|
||||
hybridApp.MainWindow.SetSize(new System.Drawing.Size(1920, 1080));
|
||||
hybridApp.MainWindow.SetTitle("ThingsGateway");
|
||||
hybridApp.MainWindow.SetIconFile("wwwroot/favicon.ico");
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
|
||||
{
|
||||
};
|
||||
|
||||
hybridApp.MainWindow.WindowClosing += (sender, e) =>
|
||||
{
|
||||
app.StopAsync();
|
||||
return false;
|
||||
};
|
||||
hybridApp.Run();
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ThingsGateway.Debug.Photino": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"distributionName": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using ThingsGateway.Debug
|
||||
@namespace ThingsGateway
|
||||
|
||||
@{
|
||||
#if NET6_0
|
||||
}
|
||||
<Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
|
||||
|
||||
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
|
||||
<Found Context="routeData">
|
||||
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
|
||||
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(Razor.BaseLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
|
||||
</Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
|
||||
|
||||
|
||||
@{
|
||||
#else
|
||||
}
|
||||
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
|
||||
<Found Context="routeData">
|
||||
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
|
||||
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(Razor.BaseLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
@{
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Import Project="$(SolutionDir)Version.props" />
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode>
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="ThingsGateway.Razor" Version="$(AdminVersion)" />
|
||||
<PackageReference Include="ThingsGateway.Debug.Razor" Version="$(PluginVersion)" />
|
||||
<PackageReference Include="Photino.NET" Version="3.1.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="6.0.33" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="8.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\ThingsGateway.Photino\Photino\**" LinkBase="Photino">
|
||||
</Compile>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
|
||||
"IgnoreConfigurationFiles": [ "" ],
|
||||
"ExternalAssemblies": [ "" ]
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
|
||||
"IgnoreConfigurationFiles": [ "" ],
|
||||
"ExternalAssemblies": [ "" ]
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>ThingsGateway.Debug</title>
|
||||
<base href="/" />
|
||||
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
|
||||
<link href="_content/BootstrapBlazor/css/motronic.min.css" rel="stylesheet">
|
||||
<link href="ThingsGateway.Debug.Photino.styles.css" rel="stylesheet" />
|
||||
<link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" />
|
||||
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
|
||||
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
|
||||
<script src="_framework/blazor.webview.js" autostart="true"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ThingsGateway.Debug
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
var resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
SuspendLayout();
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(9F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1902, 1033);
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
Margin = new Padding(2);
|
||||
Name = "MainForm";
|
||||
Text = "ThingsGateway";
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components.WebView;
|
||||
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ThingsGateway.Debug
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
protected string UploadPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "uploads");
|
||||
private BlazorWebView blazorWebView;
|
||||
|
||||
|
||||
public MainForm(IServiceProvider serviceProvider)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
//默认全屏
|
||||
//this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
|
||||
//this.FormBorderStyle =FormBorderStyle.None;
|
||||
//this.TopMost = true;
|
||||
//this.KeyPreview = true;
|
||||
KeyUp += new System.Windows.Forms.KeyEventHandler(MainForm_KeyUp);
|
||||
|
||||
blazorWebView = new BlazorWebView()
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
HostPage = "wwwroot/index.html",
|
||||
Services = serviceProvider
|
||||
};
|
||||
|
||||
FormClosing += Program.Closing;
|
||||
|
||||
blazorWebView.RootComponents.Add<Routes>("#app");
|
||||
Controls.Add(blazorWebView);
|
||||
blazorWebView.BringToFront();
|
||||
blazorWebView.KeyUp += new System.Windows.Forms.KeyEventHandler(MainForm_KeyUp);
|
||||
|
||||
blazorWebView.BlazorWebViewInitialized += BlazorWebViewInitialized;
|
||||
|
||||
blazorWebView.UrlLoading +=
|
||||
(sender, urlLoadingEventArgs) =>
|
||||
{
|
||||
if (urlLoadingEventArgs.Url.Host != "0.0.0.0")
|
||||
{
|
||||
//外部链接WebView内打开,例如pdf浏览器
|
||||
Console.WriteLine(urlLoadingEventArgs.Url);
|
||||
urlLoadingEventArgs.UrlLoadingStrategy =
|
||||
UrlLoadingStrategy.OpenInWebView;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void BlazorWebViewInitialized(object? sender, EventArgs e)
|
||||
{
|
||||
//下载开始时引发 DownloadStarting,阻止默认下载
|
||||
blazorWebView.WebView.CoreWebView2.DownloadStarting += CoreWebView2_DownloadStarting;
|
||||
|
||||
//指定下载保存位置
|
||||
blazorWebView.WebView.CoreWebView2.Profile.DefaultDownloadFolderPath = UploadPath;
|
||||
|
||||
////[无依赖发布webview2程序] 固定版本运行时环境的方式来实现加载网页
|
||||
////设置web用户文件夹
|
||||
//var browserExecutableFolder = "c:\\wb2";
|
||||
//var userData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "BlazorWinFormsApp");
|
||||
//Directory.CreateDirectory(userData);
|
||||
//var creationProperties = new CoreWebView2CreationProperties()
|
||||
//{
|
||||
// UserDataFolder = userData,
|
||||
// BrowserExecutableFolder = browserExecutableFolder
|
||||
//};
|
||||
//mainBlazorWebView.WebView.CreationProperties = creationProperties;
|
||||
}
|
||||
|
||||
private void CoreWebView2_DownloadStarting(object? sender, CoreWebView2DownloadStartingEventArgs e)
|
||||
{
|
||||
var downloadOperation = e.DownloadOperation;
|
||||
string fileName = Path.GetFileName(e.ResultFilePath);
|
||||
var filePath = Path.Combine(UploadPath, fileName);
|
||||
|
||||
//指定下载保存位置
|
||||
e.ResultFilePath = filePath;
|
||||
MessageBox.Show($"下载文件完成 {fileName}", "提示");
|
||||
}
|
||||
|
||||
private void MainForm_KeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Escape)
|
||||
{
|
||||
if (WindowState == System.Windows.Forms.FormWindowState.Normal)
|
||||
{
|
||||
WindowState = System.Windows.Forms.FormWindowState.Maximized;
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = System.Windows.Forms.FormWindowState.Normal;
|
||||
FormBorderStyle = FormBorderStyle.Sizable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgvCjzYkwo82JsKPNifCjzYqwo82IcKPNgLCjzYAAAAAAAAA
|
||||
AAAAAAAAAAAAAMKPNgDCjzYBwo82HsKPNknCjzZewo82QcKPNhLCjzYAwo82AAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82S8KPNtPCjzbiwo8258KPNuDCjzatwo82EsKP
|
||||
NgAAAAAAAAAAAAAAAADCjzYAwo82BsKPNmvCjzbbwo826MKPNtvCjzbhwo82vcKPNmDCjzYywo82AAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKPNgDCjzYPwo82O8KPNs7Cjzb/wo82iMKP
|
||||
Nh7CjzYCwo82AAAAAAAAAAAAwo82AMKPNgDCjzZnwo8298KPNsLCjzY6wo82GsKPNjTCjzbJwo82/8KP
|
||||
NpTCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKPNgDCjzYIwo82vsKP
|
||||
Nv/CjzZowo82AAAAAAAAAAAAAAAAAAAAAADCjzYAwo82JMKPNtnCjzbxwo82QcKPNgDCjzYAwo82AMKP
|
||||
NpHCjzb/wo82lsKPNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwo82AMKP
|
||||
NgjCjza9wo82/8KPNmjCjzYAAAAAAAAAAAAAAAAAwo82AMKPNgDCjzZ2wo82/8KPNrnCjzYKwo82AAAA
|
||||
AADCjzYAwo82jcKPNv/CjzaWwo82AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADCjzYAwo82CMKPNr3Cjzb/wo82aMKPNgAAAAAAAAAAAAAAAADCjzYAwo82CcKPNrrCjzb/wo82fcKP
|
||||
NgDCjzYAAAAAAMKPNgDCjzaLwo82/8KPNpbCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAMKPNgDCjzYIwo82vMKPNv/CjzZpwo82AAAAAAAAAAAAAAAAAMKPNgDCjzYfwo8238KP
|
||||
Nv3CjzZRwo82AMKPNgDCjzYBwo82B8KPNpnCjzb/wo82psKPNgrCjzYAwo82AAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgjCjza8wo82/8KPNmnCjzYAAAAAAAAAAAAAAAAAwo82AMKP
|
||||
NjbCjzbxwo829sKPNj7CjzYAwo82AMKPNiPCjzaywo827cKPNv/Cjzbywo82qMKPNhPCjzYAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82CMKPNrvCjzb/wo82acKPNgAAAAAAAAAAAAAA
|
||||
AADCjzYAwo82P8KPNvfCjzbzwo82OcKPNgDCjzYAwo82D8KPNlnCjzZiwo82X8KPNmDCjzZRwo82CcKP
|
||||
NgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKPNgDCjzYHwo82usKPNv/CjzZpwo82AAAA
|
||||
AAAAAAAAAAAAAMKPNgDCjzY+wo829sKPNvTCjzY8wo82AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgfCjza6wo82/8KP
|
||||
NmnCjzYAAAAAAAAAAAAAAAAAwo82AMKPNirCjzbpwo82+MKPNkDCjzYAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82B8KP
|
||||
NrnCjzb/wo82acKPNgAAAAAAAAAAAAAAAADCjzYAwo82FcKPNtLCjzb/wo82VsKPNgAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgLCjzYTwo82BMKP
|
||||
NgDCjzYHwo82t8KPNv/CjzZpwo82AMKPNgDCjzYMwo82E8KPNgDCjzYCwo82n8KPNv/CjzaEwo82AMKP
|
||||
NgAAAAAAwo82AMKPNgDCjzZPwo82XcKPNgLCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82F8KP
|
||||
NrTCjzY6wo82AMKPNgbCjza0wo82/8KPNmnCjzYAwo82AMKPNnPCjzaawo82A8KPNgDCjzZOwo82+MKP
|
||||
NsbCjzYRwo82AAAAAADCjzYAwo82DcKPNsLCjzagwo82AMKPNgAAAAAAAAAAAAAAAAAAAAAAAAAAAMKP
|
||||
NgDCjzYPwo82ysKPNpPCjzYBwo82BcKPNrHCjzb/wo82acKPNgDCjzYUwo82zMKPNpPCjzYAwo82AMKP
|
||||
NgrCjzanwo82/MKPNmnCjzYAwo82AMKPNgDCjzZRwo8298KPNnbCjzYAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAwo82AMKPNgDCjzaawo827cKPNmHCjzYswo82vMKPNv/CjzaEwo82K8KPNoDCjzb5wo82ZMKP
|
||||
NgAAAAAAwo82AMKPNi7CjzbWwo825sKPNlnCjzYdwo82SMKPNtTCjzb9wo82UsKPNgAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNmHCjzb3wo828cKPNt/Cjzbuwo8298KPNurCjzbjwo829MKP
|
||||
NurCjzY6wo82AAAAAADCjzYAwo82AMKPNjfCjza8wo826cKPNtvCjzbewo82ycKPNs7CjzYvwo82AAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82EsKPNj3CjzZAwo82QcKPNkDCjzY/wo82QMKP
|
||||
NkDCjzY/wo82OMKPNgrCjzYAAAAAAAAAAADCjzYAwo82AMKPNg/CjzY5wo82SsKPNjDCjzYOwo82G8KP
|
||||
NgXCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////wH4H/8B8Af/AfAH/4f
|
||||
hx/+H4cf/h8PH/4fDAf+HwwH/h8MB/4fD//+Hw///h8P/+IZD4/iGIcP4BGHH+ABwB/wAeAf8AHwH///
|
||||
//////////////////////////////////8=
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,87 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using ThingsGateway.NewLife.Log;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
internal static void Closing(object? sender, FormClosingEventArgs e)
|
||||
{
|
||||
host.StopAsync();
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
//当前工作目录设为程序集的基目录
|
||||
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
|
||||
// 增加中文编码支持
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
#region 控制台输出Logo
|
||||
|
||||
Console.Write(Environment.NewLine);
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
XTrace.WriteLine(string.Empty);
|
||||
Console.WriteLine(
|
||||
"""
|
||||
|
||||
_______ _ _ _____ _
|
||||
|__ __|| | (_) / ____| | |
|
||||
| | | |__ _ _ __ __ _ ___ | | __ __ _ | |_ ___ __ __ __ _ _ _
|
||||
| | | '_ \ | || '_ \ / _` |/ __|| | |_ | / _` || __|/ _ \\ \ /\ / // _` || | | |
|
||||
| | | | | || || | | || (_| |\__ \| |__| || (_| || |_| __/ \ V V /| (_| || |_| |
|
||||
|_| |_| |_||_||_| |_| \__, ||___/ \_____| \__,_| \__|\___| \_/\_/ \__,_| \__, |
|
||||
__/ | __/ |
|
||||
|___/ |___/
|
||||
|
||||
"""
|
||||
);
|
||||
Console.ResetColor();
|
||||
|
||||
#endregion 控制台输出Logo
|
||||
|
||||
|
||||
var options = GenericRunOptions.DefaultSilence
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddWindowsFormsBlazorWebView();
|
||||
});
|
||||
;
|
||||
|
||||
Serve.BuildApplication(options, out var app);
|
||||
host = app;
|
||||
app.Start();
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
|
||||
{
|
||||
MessageBox.Show(text: error.ExceptionObject.ToString(), caption: "Error");
|
||||
};
|
||||
|
||||
Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MainForm(app.Services));
|
||||
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
|
||||
|
||||
private static IHost host;
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using ThingsGateway.Debug
|
||||
@namespace ThingsGateway
|
||||
|
||||
@{
|
||||
#if NET6_0
|
||||
}
|
||||
<Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
|
||||
|
||||
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
|
||||
<Found Context="routeData">
|
||||
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
|
||||
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(Razor.BaseLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
|
||||
</Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
|
||||
|
||||
|
||||
@{
|
||||
#else
|
||||
}
|
||||
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
|
||||
<Found Context="routeData">
|
||||
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
|
||||
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(Razor.BaseLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
@{
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<Import Project="$(SolutionDir)Version.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
||||
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<TargetFrameworks>net8.0-windows;</TargetFrameworks>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode>
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="ThingsGateway.Razor" Version="$(AdminVersion)" />
|
||||
<PackageReference Include="ThingsGateway.Debug.Razor" Version="$(PluginVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="8.0.91" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<EmbeddedResource Include="..\ThingsGateway.Debug.Photino\Locales\en-US.json" Link="Locales\en-US.json" />
|
||||
<EmbeddedResource Include="..\ThingsGateway.Debug.Photino\Locales\zh-CN.json" Link="Locales\zh-CN.json" />
|
||||
<EmbeddedResource Include="..\ThingsGateway.Debug.Photino\Locales\zh-TW.json">
|
||||
<Link>Locales\zh-TW.json</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Locales\*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\ThingsGateway.Debug.Photino\MainLayout.razor" Link="MainLayout.razor" />
|
||||
<Compile Include="..\ThingsGateway.Debug.Photino\MainLayout.razor.cs" Link="MainLayout.razor.cs" />
|
||||
<Content Include="..\ThingsGateway.Debug.Photino\MainLayout.razor.css" Link="MainLayout.razor.css" />
|
||||
<Content Include="..\ThingsGateway.Debug.Photino\Configuration\*" LinkBase="Configuration">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
|
||||
"IgnoreConfigurationFiles": [ "" ],
|
||||
"ExternalAssemblies": [ "" ]
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
|
||||
"IgnoreConfigurationFiles": [ "" ],
|
||||
"ExternalAssemblies": [ "" ]
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>ThingsGateway.Debug</title>
|
||||
<base href="/" />
|
||||
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
|
||||
<link href="_content/BootstrapBlazor/css/motronic.min.css" rel="stylesheet">
|
||||
<link href="ThingsGateway.Debug.Winform.styles.css" rel="stylesheet" />
|
||||
<link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" />
|
||||
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
|
||||
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
|
||||
<script src="_framework/blazor.webview.js" autostart="true"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +1,29 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<AdminVersion>7.0.0.63</AdminVersion>
|
||||
<PluginVersion>9.0.0.13</PluginVersion>
|
||||
<ProPluginVersion>9.0.0.16</ProPluginVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;</NoWarn>
|
||||
<TargetFrameworks>net8.0;net6.0;</TargetFrameworks>
|
||||
<AdminVersion>7.2.0.64</AdminVersion>
|
||||
<GatewayVersion>7.2.3.9</GatewayVersion>
|
||||
<PluginVersion>9.0.3.10</PluginVersion>
|
||||
<ProPluginVersion>9.0.3.6</ProPluginVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
<AnalysisModeDesign>None</AnalysisModeDesign>
|
||||
<AnalysisModeDocumentation>All</AnalysisModeDocumentation>
|
||||
<AnalysisModeGlobalization>None</AnalysisModeGlobalization>
|
||||
<AnalysisModeInteroperability>All</AnalysisModeInteroperability>
|
||||
<AnalysisModeMaintainability>All</AnalysisModeMaintainability>
|
||||
<AnalysisModeNaming>None</AnalysisModeNaming>
|
||||
<AnalysisModePerformance>All</AnalysisModePerformance>
|
||||
<AnalysisModeSingleFile>All</AnalysisModeSingleFile>
|
||||
<AnalysisModeReliability>All</AnalysisModeReliability>
|
||||
<AnalysisModeSecurity>All</AnalysisModeSecurity>
|
||||
<AnalysisModeUsage>None</AnalysisModeUsage>
|
||||
<AnalysisModeStyle>None</AnalysisModeStyle>
|
||||
|
||||
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;</NoWarn>
|
||||
<TargetFrameworks>net9.0;net8.0;net6.0;</TargetFrameworks>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -24,7 +39,7 @@
|
||||
<ItemGroup>
|
||||
<None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
<DebugType>Embedded</DebugType>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net462;netstandard2.0;net8.0;net6.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net462;netstandard2.0;net9.0;net8.0;net6.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using CSScripting;
|
||||
|
||||
using CSScriptLib;
|
||||
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 脚本扩展方法
|
||||
/// </summary>
|
||||
public static class CSharpScriptEngineExtension
|
||||
{
|
||||
private static string CacheKey = $"{nameof(CSharpScriptEngineExtension)}-{nameof(Do)}";
|
||||
|
||||
private static object m_waiterLock = new object();
|
||||
|
||||
/// <summary>清理计时器</summary>
|
||||
private static TimerX? _clearTimer;
|
||||
static CSharpScriptEngineExtension()
|
||||
{
|
||||
if (_clearTimer == null)
|
||||
{
|
||||
_clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true };
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveNotAlive(Object? state)
|
||||
{
|
||||
//检测缓存
|
||||
try
|
||||
{
|
||||
var data = Instance.GetAll();
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000)
|
||||
{
|
||||
Instance.Remove(item.Key);
|
||||
item.Value?.Value?.TryDispose();
|
||||
item.Value?.Value?.GetType().Assembly.Unload();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static MemoryCache Instance { get; set; } = new MemoryCache();
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值
|
||||
/// </summary>
|
||||
public static T Do<T>(string source, params Assembly[] assemblies) where T : class
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
var _body = new StringBuilder();
|
||||
src.ToList().ForEach(l =>
|
||||
{
|
||||
if (l.StartsWith("using "))
|
||||
{
|
||||
_using.AppendLine(l);
|
||||
}
|
||||
else
|
||||
{
|
||||
_body.AppendLine(l);
|
||||
}
|
||||
|
||||
});
|
||||
var evaluator = CSScript.Evaluator;
|
||||
foreach (var item in assemblies)
|
||||
{
|
||||
evaluator = evaluator.ReferenceAssembly(item.Location);
|
||||
}
|
||||
// 动态加载并执行代码
|
||||
runScript = evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<T>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
{_using}
|
||||
{_body}
|
||||
");
|
||||
GC.Collect();
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
return runScript;
|
||||
}
|
||||
|
||||
public static void SetExpire(string source, TimeSpan? timeSpan = null)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
Instance.SetExpire(field, timeSpan ?? TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using CSScripting;
|
||||
|
||||
using CSScriptLib;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 读写表达式脚本
|
||||
/// </summary>
|
||||
public interface ReadWriteExpressions
|
||||
{
|
||||
public TouchSocket.Core.ILog? Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取新值
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <returns></returns>
|
||||
object GetNewValue(object a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式扩展
|
||||
/// </summary>
|
||||
public static class ExpressionEvaluatorExtension
|
||||
{
|
||||
private static string CacheKey = $"{nameof(ExpressionEvaluatorExtension)}-{nameof(GetReadWriteExpressions)}";
|
||||
|
||||
private static object m_waiterLock = new object();
|
||||
|
||||
/// <summary>清理计时器</summary>
|
||||
private static TimerX? _clearTimer;
|
||||
static ExpressionEvaluatorExtension()
|
||||
{
|
||||
if (_clearTimer == null)
|
||||
{
|
||||
_clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true };
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveNotAlive(Object? state)
|
||||
{
|
||||
//检测缓存
|
||||
try
|
||||
{
|
||||
var data = Instance.GetAll();
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000)
|
||||
{
|
||||
Instance.Remove(item.Key);
|
||||
item.Value?.Value?.TryDispose();
|
||||
item.Value?.Value?.GetType().Assembly.Unload();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static MemoryCache Instance { get; set; } = new MemoryCache();
|
||||
|
||||
/// <summary>
|
||||
/// 添加或获取脚本,非线程安全
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
public static ReadWriteExpressions GetOrAddScript(string source)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
if (!source.Contains("return"))
|
||||
{
|
||||
source = $"return {source}";//只判断简单脚本中可省略return字符串
|
||||
}
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
var _body = new StringBuilder();
|
||||
src.ToList().ForEach(l =>
|
||||
{
|
||||
if (l.StartsWith("using "))
|
||||
{
|
||||
_using.AppendLine(l);
|
||||
}
|
||||
else
|
||||
{
|
||||
_body.AppendLine(l);
|
||||
}
|
||||
});
|
||||
// 动态加载并执行代码
|
||||
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
{_using}
|
||||
public class Script:ReadWriteExpressions
|
||||
{{
|
||||
public TouchSocket.Core.ILog? Logger {{ get; set; }}
|
||||
public object GetNewValue(object raw)
|
||||
{{
|
||||
{_body};
|
||||
}}
|
||||
}}
|
||||
");
|
||||
GC.Collect();
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
|
||||
return runScript;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算表达式:例如:(int)raw*100,raw为原始值
|
||||
/// </summary>
|
||||
public static object GetExpressionsResult(this string expressions, object rawvalue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expressions))
|
||||
{
|
||||
return rawvalue;
|
||||
}
|
||||
var readWriteExpressions = GetReadWriteExpressions(expressions);
|
||||
var value = readWriteExpressions.GetNewValue(rawvalue);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算表达式:例如:(int)raw*100,raw为原始值
|
||||
/// </summary>
|
||||
public static object GetExpressionsResult(this string expressions, object rawvalue, ILog logger)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expressions))
|
||||
{
|
||||
return rawvalue;
|
||||
}
|
||||
var readWriteExpressions = GetReadWriteExpressions(expressions);
|
||||
readWriteExpressions.Logger = logger;
|
||||
var value = readWriteExpressions.GetNewValue(rawvalue);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值ReadWriteExpressions
|
||||
/// </summary>
|
||||
public static ReadWriteExpressions GetReadWriteExpressions(string source)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
runScript = GetOrAddScript(source);
|
||||
}
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
return runScript;
|
||||
}
|
||||
public static void SetExpire(string source, TimeSpan? timeSpan = null)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
Instance.SetExpire(field, timeSpan ?? TimeSpan.FromHours(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net9.0;net8.0;net6.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="CS-Script" Version="4.8.21" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net9.0'">
|
||||
<PackageReference Include="CS-Script" Version="4.8.19" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,115 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Core.Json.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 调试UI
|
||||
/// </summary>
|
||||
public abstract class AdapterDebugBase : ComponentBase, IDisposable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
~AdapterDebugBase()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 长度
|
||||
/// </summary>
|
||||
public int ArrayLength { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 默认读写设备
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IProtocol Plc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量地址
|
||||
/// </summary>
|
||||
public string RegisterAddress { get; set; } = "400001";
|
||||
|
||||
/// <summary>
|
||||
/// 写入值
|
||||
/// </summary>
|
||||
public string WriteValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
protected DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
|
||||
|
||||
[Inject]
|
||||
private IStringLocalizer<AdapterDebugBase> Localizer { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Plc?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task ReadAsync()
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.ReadAsync(RegisterAddress, ArrayLength, DataType);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Plc.Logger?.LogInformation(data.Content.ToJsonNetString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Plc.Logger?.Warning(data.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plc.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task WriteAsync()
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.WriteAsync(RegisterAddress, WriteValue.GetJTokenFromString(), DataType);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Plc.Logger?.LogInformation($" {WriteValue.GetJTokenFromString()} {Localizer["WriteSuccess"]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Plc.Logger?.Warning(data.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plc.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using Microsoft.JSInterop;
|
||||
@using ThingsGateway.Core.Json.Extension
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@using BootstrapBlazor.Components
|
||||
@namespace ThingsGateway.Debug
|
||||
@inherits AdapterDebugBase
|
||||
|
||||
<div class=@($"{ClassString} row my-2 mx-2") style="min-height:500px;height: 50%;">
|
||||
|
||||
<div class="col-12 col-md-5 h-100">
|
||||
<Tab class="h-100">
|
||||
<TabItem Text=@Localizer["CommonFunctions"]>
|
||||
@if (ShowDefaultReadWriteContent)
|
||||
{
|
||||
|
||||
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
|
||||
<div class="p-1">
|
||||
|
||||
<BootstrapInputNumber @bind-Value=@ArrayLength ShowLabel="true" />
|
||||
<Select @bind-Value="@DataType" ShowLabel="true" IsPopover="true" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@Localizer["Read"]</Button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
|
||||
ShowLabel="true" />
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@Localizer["Write"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
@if (ReadWriteContent != null)
|
||||
{
|
||||
@ReadWriteContent
|
||||
}
|
||||
</TabItem>
|
||||
|
||||
|
||||
<TabItem Text=@Localizer["SpecialFunctions"]>
|
||||
@if (ShowDefaultOtherContent)
|
||||
{
|
||||
|
||||
@foreach (var item in VariableRunTimes)
|
||||
{
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
|
||||
<div class="p-1">
|
||||
|
||||
<BootstrapInput @bind-Value=@item.RegisterAddress title="@Plc?.GetAddressDescription()"
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
<Select @bind-Value="@item.DataType" ShowLabel="true" IsPopover />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
|
||||
|
||||
|
||||
}
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tab>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-7 ">
|
||||
<LogConsole LogLevel=@((Plc?.Logger??Logger)?.LogLevel??TouchSocket.Core.LogLevel.Trace) LogLevelChanged="(a)=>{
|
||||
var log=Plc?.Logger??Logger;
|
||||
if(log!=null)
|
||||
log.LogLevel=a;
|
||||
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class AdapterDebugComponent : AdapterDebugBase
|
||||
{
|
||||
/// <summary>
|
||||
/// MaxPack
|
||||
/// </summary>
|
||||
public int MaxPack = 100;
|
||||
|
||||
/// <summary>
|
||||
/// VariableRunTimes
|
||||
/// </summary>
|
||||
public List<VariableClass> VariableRunTimes;
|
||||
|
||||
[Parameter]
|
||||
public string ClassString { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string HeaderText { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string LogPath { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ILog Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment OtherContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment ReadWriteContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool ShowDefaultOtherContent { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowDefaultReadWriteContent { get; set; } = true;
|
||||
|
||||
[Inject]
|
||||
private IStringLocalizer<AdapterDebugComponent> Localizer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MulReadAsync
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task MulRead()
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
var deviceVariableSourceReads = Plc.LoadSourceRead<VariableSourceClass>(VariableRunTimes, MaxPack, "1000");
|
||||
foreach (var item in deviceVariableSourceReads)
|
||||
{
|
||||
var result = await Plc.ReadAsync(item.RegisterAddress, item.Length);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result1 = item.VariableRunTimes.PraseStructContent(Plc, result.Content, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
Plc.Logger?.Warning(result1.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plc.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.LastErrorMessage = result.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
Plc.Logger?.Warning(result.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
VariableRunTimes = new()
|
||||
{
|
||||
new VariableClass()
|
||||
{
|
||||
DataType=DataTypeEnum.Int16,
|
||||
RegisterAddress="40001",
|
||||
IntervalTime="1000",
|
||||
},
|
||||
new VariableClass()
|
||||
{
|
||||
DataType=DataTypeEnum.Int32,
|
||||
RegisterAddress="40011",
|
||||
IntervalTime="1000",
|
||||
},
|
||||
};
|
||||
|
||||
HeaderText = Localizer[nameof(HeaderText)];
|
||||
base.OnInitialized();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
@namespace ThingsGateway.Debug
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using ThingsGateway.Foundation
|
||||
@using TouchSocket.Core
|
||||
@using BootstrapBlazor.Components
|
||||
|
||||
<Card>
|
||||
<BodyTemplate>
|
||||
<ValidateForm Model="Model" OnValidSubmit="ValidSubmit">
|
||||
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=3 LabelWidth=100 Model="Model" >
|
||||
|
||||
<FieldItems>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelType">
|
||||
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-sm-6 col-md-4">
|
||||
<Select SkipValidate="true" @bind-Value="@value.ChannelType" OnSelectedItemChanged=@((a)=>InvokeAsync(StateHasChanged)) />
|
||||
</div>
|
||||
</EditTemplate>
|
||||
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.RemoteUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.BindUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession&&context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
|
||||
<EditorItem @bind-Field="@context.PortName" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.BaudRate" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DataBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.Parity" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.StopBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
|
||||
</FieldItems>
|
||||
<Buttons>
|
||||
<Button ButtonType="ButtonType.Submit" IsAsync class="mx-2" Color=Color.Primary OnClick="ConfimClick">@Localizer["Confim"]</Button>
|
||||
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="ConnectClick">@Localizer["Connect"]</Button>
|
||||
<Button IsAsync class="mx-2" Color=Color.Warning OnClick="DisconnectClick">@Localizer["Disconnect"]</Button>
|
||||
</Buttons>
|
||||
</EditorForm>
|
||||
|
||||
</ValidateForm>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
@@ -0,0 +1,144 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class ChannelDataDebugComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string ClassString { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ChannelData> OnConnectClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ChannelData> OnConfimClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnDisConnectClick { get; set; }
|
||||
|
||||
private ChannelData? Model { get; set; } = new();
|
||||
|
||||
private IEnumerable<SelectedItem> ChannelDataItems { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IStringLocalizer<ChannelDataDebugComponent> Localizer { get; set; }
|
||||
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
public Task ValidSubmit(EditContext editContext)
|
||||
{
|
||||
CheckInput(Model);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void CheckInput(ChannelData input)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (input.ChannelType == ChannelTypeEnum.TcpClient)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.RemoteUrl))
|
||||
throw new(Localizer["RemoteUrlNotNull"]);
|
||||
}
|
||||
else if (input.ChannelType == ChannelTypeEnum.TcpService)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.BindUrl))
|
||||
throw new(Localizer["BindUrlNotNull"]);
|
||||
}
|
||||
else if (input.ChannelType == ChannelTypeEnum.UdpSession)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.BindUrl) && string.IsNullOrEmpty(input.RemoteUrl))
|
||||
throw new(Localizer["BindUrlOrRemoteUrlNotNull"]);
|
||||
}
|
||||
else if (input.ChannelType == ChannelTypeEnum.SerialPort)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.PortName))
|
||||
throw new(Localizer["PortNameNotNull"]);
|
||||
if (input.BaudRate == null)
|
||||
throw new(Localizer["BaudRateNotNull"]);
|
||||
if (input.DataBits == null)
|
||||
throw new(Localizer["DataBitsNotNull"]);
|
||||
if (input.Parity == null)
|
||||
throw new(Localizer["ParityNotNull"]);
|
||||
if (input.StopBits == null)
|
||||
throw new(Localizer["StopBitsNotNull"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new(Localizer["NotOther"]);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToastService.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisconnectClick()
|
||||
{
|
||||
if (Model?.Channel != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Model.Channel.CloseAsync(DefaultResource.Localizer["ProactivelyDisconnect", nameof(DisconnectClick)]);
|
||||
if (OnDisConnectClick.HasDelegate)
|
||||
await OnDisConnectClick.InvokeAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfimClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ChannelData.CreateChannelAsync(Model);
|
||||
if (OnConfimClick.HasDelegate)
|
||||
await OnConfimClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel?.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectClick()
|
||||
{
|
||||
if (Model != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Model.Channel != null)
|
||||
if (OnConnectClick.HasDelegate)
|
||||
await OnConnectClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop;
|
||||
@using ThingsGateway.NewLife.Threading
|
||||
@using ThingsGateway.Extension;
|
||||
@using BootstrapBlazor.Components
|
||||
@namespace ThingsGateway.Debug
|
||||
|
||||
<Card HeaderText=@HeaderText class="mt-2" style="width:100%">
|
||||
<HeaderTemplate>
|
||||
<div class="flex-fill">
|
||||
</div>
|
||||
@if (LogLevelChanged.HasDelegate)
|
||||
{
|
||||
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
|
||||
}
|
||||
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(IsPause?"fa fa-play":"fa fa-pause") OnClick="Pause" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
|
||||
|
||||
</HeaderTemplate>
|
||||
<BodyTemplate>
|
||||
<div style=@($"height:{HeightString};overflow-y:scroll")>
|
||||
<Virtualize Items="CurrentMessages??new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
|
||||
<ItemContent>
|
||||
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
|
||||
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
|
||||
itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text ":"green--text ")
|
||||
style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;"
|
||||
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
|
||||
|
||||
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
|
||||
|
||||
</div>
|
||||
@* </Tooltip> *@
|
||||
</ItemContent>
|
||||
</Virtualize>
|
||||
</div>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class LogConsole : IDisposable
|
||||
{
|
||||
private bool IsPause;
|
||||
|
||||
public bool Disposed { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public LogLevel LogLevel { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<LogLevel> LogLevelChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string HeaderText { get; set; } = "Log";
|
||||
|
||||
[Parameter]
|
||||
public string HeightString { get; set; } = "400px";
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string LogPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
public ICollection<LogMessage> Messages { get; set; } = new List<LogMessage>();
|
||||
|
||||
private ICollection<LogMessage> CurrentMessages => IsPause ? PauseMessagesText : Messages;
|
||||
|
||||
[Inject]
|
||||
private DownloadService DownloadService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 暂停缓存
|
||||
/// </summary>
|
||||
private ICollection<LogMessage> PauseMessagesText { get; set; } = new List<LogMessage>();
|
||||
|
||||
[Inject]
|
||||
private IPlatformService PlatformService { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
await ExecuteAsync();
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected async Task ExecuteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(LogPath);
|
||||
if (files == null || files.FirstOrDefault() == null || !files.FirstOrDefault().IsSuccess)
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
var result = TextFileReader.LastLog(files.FirstOrDefault().FullName, 0);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
}
|
||||
sw.Stop();
|
||||
if (sw.ElapsedMilliseconds > 500)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(LogPath);
|
||||
if (files == null || files.FirstOrDefault() == null || !files.FirstOrDefault().IsSuccess)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in files)
|
||||
{
|
||||
if (File.Exists(item.FullName))
|
||||
{
|
||||
int error = 0;
|
||||
while (error < 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.SetAttributes(item.FullName, FileAttributes.Normal);
|
||||
File.Delete(item.FullName);
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
error++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleOnExportClick(MouseEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPause)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using StreamWriter writer = new(memoryStream);
|
||||
foreach (var item in PauseMessagesText)
|
||||
{
|
||||
await writer.WriteLineAsync(item.Message);
|
||||
}
|
||||
await writer.FlushAsync();
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// 定义文件名称规则的正则表达式模式
|
||||
string pattern = @"[\\/:*?""<>|]";
|
||||
// 使用正则表达式将不符合规则的部分替换为下划线
|
||||
string sanitizedFileName = Regex.Replace(HeaderText, pattern, "_");
|
||||
await DownloadService.DownloadFromStreamAsync(sanitizedFileName + DateTime.Now.ToFileDateTimeFormat(), memoryStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PlatformService != null)
|
||||
await PlatformService.OnLogExport(LogPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Task Pause()
|
||||
{
|
||||
IsPause = !IsPause;
|
||||
if (IsPause)
|
||||
PauseMessagesText = Messages.ToList();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LogMessage
|
||||
{
|
||||
public LogMessage(int level, string message)
|
||||
{
|
||||
Level = level;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public int Level { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"ThingsGateway.Debug.ChannelDataDebugComponent": {
|
||||
"Name": "Name",
|
||||
"Name.Required": "{0} cannot be empty",
|
||||
"ChannelType": "Channel Type",
|
||||
"Enable": "Enable",
|
||||
"LogEnable": "Enable Debug Log",
|
||||
"RemoteUrl": "Remote IP Address",
|
||||
"BindUrl": "Local Bind IP Address",
|
||||
"PortName": "COM Port",
|
||||
"BaudRate": "Baud Rate",
|
||||
"DataBits": "Data Bits",
|
||||
"Parity": "Parity",
|
||||
"StopBits": "Stop Bits",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts",
|
||||
"RemoteUrlNotNull": "Remote IP Address cannot be empty",
|
||||
"BindUrlNotNull": "Local Bind IP Address cannot be empty",
|
||||
"BindUrlOrRemoteUrlNotNull": "Remote IP Address or Local Bind IP Address cannot be empty",
|
||||
"PortNameNotNull": "COM Port cannot be empty",
|
||||
"BaudRateNotNull": "Baud Rate cannot be empty",
|
||||
"DataBitsNotNull": "Data Bits can be empty",
|
||||
"ParityNotNull": "Parity cannot be empty",
|
||||
"StopBitsNotNull": "Stop Bits cannot be empty",
|
||||
"SaveChannel": "Add/Modify Channel",
|
||||
"DeleteChannel": "Delete Channel",
|
||||
"ClearChannel": "Clear Channel",
|
||||
"ExportChannel": "Export Channel",
|
||||
"ImportChannel": "Import Channel",
|
||||
"ImportNullError": "Unable to recognize",
|
||||
"NotOther": "Not supporting other channel types",
|
||||
"Connect": "Connect",
|
||||
"Confim": "Confim",
|
||||
"Disconnect": "Disconnect",
|
||||
"Channel": "Channel"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.AdapterDebugBase": {
|
||||
"WriteSuccess": "Write Successful",
|
||||
"DefaultSend": "Direct Send",
|
||||
"Send": "Send",
|
||||
"DataType": "Data Type",
|
||||
"RegisterAddress": "Register Address",
|
||||
"ArrayLength": "Array Length",
|
||||
"WriteValue": "Write Value",
|
||||
"SendValue": "Send Raw Message"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugComponent": {
|
||||
"HeaderText": "Channel Log",
|
||||
"WriteSuccess": "Write Successful",
|
||||
"DefaultSend": "Direct Send",
|
||||
"Send": "Send",
|
||||
"DataType": "Data Type",
|
||||
"RegisterAddress": "Register Address",
|
||||
"ArrayLength": "Array Length",
|
||||
"WriteValue": "Write Value",
|
||||
"SendValue": "Send Raw Message",
|
||||
"CommonFunctions": "Common Functions",
|
||||
"SpecialFunctions": "Special Functions",
|
||||
"Read": "Read",
|
||||
"Write": "Write",
|
||||
"MulRead": "Multiple Read"
|
||||
},
|
||||
"ThingsGateway.Debug.ChannelDataEditComponent": {
|
||||
"BasicInformation": "Basic Information",
|
||||
"Connection": "Connection"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"ThingsGateway.Debug.ChannelDataDebugComponent": {
|
||||
"Name": "名称",
|
||||
"Name.Required": " {0} 不可为空",
|
||||
"ChannelType": "通道类型",
|
||||
"Enable": "启用",
|
||||
"LogEnable": "启用调试日志",
|
||||
|
||||
"RemoteUrl": "远程IP地址",
|
||||
"BindUrl": "本地绑定IP地址",
|
||||
|
||||
"PortName": "COM口",
|
||||
"BaudRate": "波特率",
|
||||
"DataBits": "数据位",
|
||||
"Parity": "校验位",
|
||||
"StopBits": "停止位",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts",
|
||||
|
||||
"RemoteUrlNotNull": "远程IP地址不可为空",
|
||||
"BindUrlNotNull": "本地绑定IP地址不可为空",
|
||||
"BindUrlOrRemoteUrlNotNull": "远程IP地址或本地绑定IP地址不可为空",
|
||||
"PortNameNotNull": "COM口不可为空",
|
||||
"BaudRateNotNull": "波特率不可为空",
|
||||
"DataBitsNotNull": "数据位可为空",
|
||||
"ParityNotNull": "校验位不可为空",
|
||||
"StopBitsNotNull": "停止位不可为空",
|
||||
|
||||
"SaveChannel": "添加/修改通道",
|
||||
"DeleteChannel": "删除通道",
|
||||
"ClearChannel": "清空通道",
|
||||
"ExportChannel": "导出通道",
|
||||
"ImportChannel": "导入通道",
|
||||
|
||||
"ImportNullError": "无法识别",
|
||||
|
||||
"NotOther": "不支持其他通道类型",
|
||||
"Connect": "连接",
|
||||
"Confim": "创建",
|
||||
"Disconnect": "断开",
|
||||
"Channel": "通道"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.AdapterDebugBase": {
|
||||
"WriteSuccess": "写入成功",
|
||||
"DefaultSend": "直接发送",
|
||||
"Send": "发送",
|
||||
"DataType": "数据类型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "数组长度",
|
||||
"WriteValue": "写入值",
|
||||
"SendValue": "发送原始报文"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugComponent": {
|
||||
"HeaderText": "通道日志",
|
||||
"WriteSuccess": "写入成功",
|
||||
"DefaultSend": "直接发送",
|
||||
"Send": "发送",
|
||||
"DataType": "数据类型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "数组长度",
|
||||
"WriteValue": "写入值",
|
||||
"SendValue": "发送原始报文",
|
||||
"CommonFunctions": "常用功能",
|
||||
"SpecialFunctions": "特殊功能",
|
||||
"Read": "读取",
|
||||
"Write": "写入",
|
||||
"MulRead": "多读"
|
||||
},
|
||||
"ThingsGateway.Debug.ChannelDataEditComponent": {
|
||||
"BasicInformation": "基础信息",
|
||||
"Connection": "连接"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"ThingsGateway.Debug.ChannelDataDebugComponent": {
|
||||
"Name": "名稱",
|
||||
"Name.Required": "{0} 不可為空",
|
||||
"ChannelType": "通道類型",
|
||||
"Enable": "啟用",
|
||||
"LogEnable": "啟用調試日誌",
|
||||
"RemoteUrl": "遠程IP地址",
|
||||
"BindUrl": "本地綁定IP地址",
|
||||
"PortName": "COM口",
|
||||
"BaudRate": "波特率",
|
||||
"DataBits": "數據位",
|
||||
"Parity": "校驗位",
|
||||
"StopBits": "停止位",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts",
|
||||
"RemoteUrlNotNull": "遠程IP地址不可為空",
|
||||
"BindUrlNotNull": "本地綁定IP地址不可為空",
|
||||
"BindUrlOrRemoteUrlNotNull": "遠程IP地址或本地綁定IP地址不可為空",
|
||||
"PortNameNotNull": "COM口不可為空",
|
||||
"BaudRateNotNull": "波特率不可為空",
|
||||
"DataBitsNotNull": "數據位可為空",
|
||||
"ParityNotNull": "校驗位不可為空",
|
||||
"StopBitsNotNull": "停止位不可為空",
|
||||
"SaveChannel": "添加/修改通道",
|
||||
"DeleteChannel": "刪除通道",
|
||||
"ClearChannel": "清空通道",
|
||||
"ExportChannel": "導出通道",
|
||||
"ImportChannel": "導入通道",
|
||||
"ImportNullError": "無法識別",
|
||||
"NotOther": "不支持其他通道類型",
|
||||
"Connect": "連接",
|
||||
"Confim": "創建",
|
||||
"Disconnect": "斷開",
|
||||
"Channel": "通道"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugBase": {
|
||||
"WriteSuccess": "寫入成功",
|
||||
"DefaultSend": "直接發送",
|
||||
"Send": "發送",
|
||||
"DataType": "數據類型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "數組長度",
|
||||
"WriteValue": "寫入值",
|
||||
"SendValue": "發送原始報文"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugComponent": {
|
||||
"HeaderText": "通道日誌",
|
||||
"WriteSuccess": "寫入成功",
|
||||
"DefaultSend": "直接發送",
|
||||
"Send": "發送",
|
||||
"DataType": "數據類型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "數組長度",
|
||||
"WriteValue": "寫入值",
|
||||
"SendValue": "發送原始報文",
|
||||
"CommonFunctions": "常用功能",
|
||||
"SpecialFunctions": "特殊功能",
|
||||
"Read": "讀取",
|
||||
"Write": "寫入",
|
||||
"MulRead": "多讀"
|
||||
},
|
||||
"ThingsGateway.Debug.ChannelDataEditComponent": {
|
||||
"BasicInformation": "基礎資訊",
|
||||
"Connection": "連接"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public class HybridPlatformService : IPlatformService
|
||||
{
|
||||
public Task OnLogExport(string logPath)
|
||||
{
|
||||
OpenFolder(logPath);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void OpenFolder(string path)
|
||||
{
|
||||
// Normalize the path for the current operating system
|
||||
path = System.IO.Path.GetFullPath(path); // Ensure the path is absolute
|
||||
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
{
|
||||
System.Diagnostics.Process.Start("explorer.exe", path);
|
||||
}
|
||||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
|
||||
{
|
||||
System.Diagnostics.Process.Start("xdg-open", path);
|
||||
}
|
||||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX))
|
||||
{
|
||||
System.Diagnostics.Process.Start("open", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public interface IPlatformService
|
||||
{
|
||||
/// <summary>
|
||||
/// OnLogExport
|
||||
/// </summary>
|
||||
/// <param name="logPath">日志文件夹路径</param>
|
||||
/// <returns></returns>
|
||||
public Task OnLogExport(string logPath);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Microsoft.JSInterop;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
22
src/Foundation/ThingsGateway.Foundation.Razor/Startup.cs
Normal file
22
src/Foundation/ThingsGateway.Foundation.Razor/Startup.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
[AppStartup(100000000)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPlatformService, PlatformService>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Admin\src\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
global using BootstrapBlazor.Components;
|
||||
|
||||
global using Microsoft.AspNetCore.Components;
|
||||
global using Microsoft.Extensions.Localization;
|
||||
global using Microsoft.Extensions.Options;
|
||||
|
||||
global using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
global using ThingsGateway.Razor;
|
||||
|
||||
[assembly: SuppressMessage("Reliability", "CA2007", Justification = "<挂起>", Scope = "module")]
|
||||
@@ -7,4 +7,3 @@
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
internal static class Utils
|
||||
{
|
||||
public static bool IsInheritFrom(this ITypeSymbol typeSymbol, string baseType)
|
||||
{
|
||||
if (typeSymbol.ToDisplayString() == baseType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeSymbol.BaseType != null)
|
||||
{
|
||||
var b = IsInheritFrom(typeSymbol.BaseType, baseType);
|
||||
if (b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in typeSymbol.AllInterfaces)
|
||||
{
|
||||
var b = IsInheritFrom(item, baseType);
|
||||
if (b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string RenameCamelCase(this string str)
|
||||
{
|
||||
var firstChar = str[0];
|
||||
|
||||
if (firstChar == char.ToLowerInvariant(firstChar))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
var name = str.ToCharArray();
|
||||
name[0] = char.ToLowerInvariant(firstChar);
|
||||
|
||||
return new string(name);
|
||||
}
|
||||
|
||||
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute)
|
||||
{
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
if (attrClass != null && attrClass.ToDisplayString() == attribute.ToDisplayString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasAttribute(this ISymbol symbol, string attribute, out AttributeData attributeData)
|
||||
{
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
if (attrClass != null && attrClass.ToDisplayString() == attribute)
|
||||
{
|
||||
attributeData = attr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
attributeData = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasFlags(int value, int flag)
|
||||
{
|
||||
return (value & flag) == flag;
|
||||
}
|
||||
|
||||
public static bool HasReturn(this IMethodSymbol method)
|
||||
{
|
||||
if (method.ReturnsVoid || method.ReturnType.ToDisplayString() == typeof(Task).FullName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,128 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
internal sealed class VariableCodeBuilder
|
||||
{
|
||||
private readonly INamedTypeSymbol m_pluginClass;
|
||||
|
||||
public VariableCodeBuilder(INamedTypeSymbol pluginClass)
|
||||
{
|
||||
m_pluginClass = pluginClass;
|
||||
}
|
||||
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public IEnumerable<string> Usings
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "using System;";
|
||||
yield return "using System.Diagnostics;";
|
||||
yield return "using ThingsGateway.Foundation;";
|
||||
yield return "using System.Threading.Tasks;";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetFileName()
|
||||
{
|
||||
return m_pluginClass.ToDisplayString() + "Generator";
|
||||
}
|
||||
|
||||
public bool TryToSourceText(out SourceText sourceText)
|
||||
{
|
||||
var code = ToString();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
{
|
||||
sourceText = null;
|
||||
return false;
|
||||
}
|
||||
sourceText = SourceText.From(code, Encoding.UTF8);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var propertys = FindPropertys().ToList();
|
||||
if (propertys.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var codeString = new StringBuilder();
|
||||
codeString.AppendLine("/*");
|
||||
codeString.AppendLine("此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码");
|
||||
codeString.AppendLine("*/");
|
||||
codeString.AppendLine("#pragma warning disable");
|
||||
|
||||
foreach (var item in Usings)
|
||||
{
|
||||
codeString.AppendLine(item);
|
||||
}
|
||||
|
||||
codeString.AppendLine($"namespace {m_pluginClass.ContainingNamespace}");
|
||||
codeString.AppendLine("{");
|
||||
codeString.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCode(\"ThingsGateway.Foundation\",\"{Assembly.GetExecutingAssembly().GetName().Version}\")]");
|
||||
codeString.AppendLine($"partial class {m_pluginClass.Name}");
|
||||
codeString.AppendLine("{");
|
||||
foreach (var item in propertys)
|
||||
{
|
||||
BuildMethod(codeString, item);
|
||||
}
|
||||
codeString.AppendLine("}");
|
||||
codeString.AppendLine("}");
|
||||
|
||||
return codeString.ToString();
|
||||
}
|
||||
|
||||
private void BuildMethod(StringBuilder stringBuilder, IPropertySymbol propertySymbol)
|
||||
{
|
||||
var attributeData = propertySymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName);
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine($"public ValueTask<OperResult> Write{propertySymbol.Name}Async({propertySymbol.Type} value,CancellationToken cancellationToken=default)");
|
||||
stringBuilder.AppendLine("{");
|
||||
stringBuilder.AppendLine($"return WriteValueAsync(\"{propertySymbol.Name}\",value,cancellationToken);");
|
||||
stringBuilder.AppendLine("}");
|
||||
stringBuilder.AppendLine();
|
||||
}
|
||||
|
||||
private IEnumerable<IPropertySymbol> FindPropertys()
|
||||
{
|
||||
return m_pluginClass
|
||||
.GetMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(m =>
|
||||
{
|
||||
return m.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 源生成
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class VariableObjectSourceGenerator : ISourceGenerator
|
||||
{
|
||||
private string m_generatorVariableAttribute = @"
|
||||
using System;
|
||||
|
||||
namespace ThingsGateway.Foundation
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用源生成变量写入方法的调用。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class GeneratorVariableAttribute:Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
//Debugger.Launch();
|
||||
context.RegisterForPostInitialization(a =>
|
||||
{
|
||||
a.AddSource(nameof(m_generatorVariableAttribute), m_generatorVariableAttribute);
|
||||
});
|
||||
context.RegisterForSyntaxNotifications(() => new VariableSyntaxReceiver());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly);
|
||||
|
||||
if (context.SyntaxReceiver is VariableSyntaxReceiver receiver)
|
||||
{
|
||||
var builders = receiver
|
||||
.GetVariableObjectTypes(context.Compilation)
|
||||
.Select(i => new VariableCodeBuilder(i))
|
||||
.Distinct();
|
||||
foreach (var builder in builders)
|
||||
{
|
||||
if (builder.TryToSourceText(out var sourceText))
|
||||
{
|
||||
var tree = CSharpSyntaxTree.ParseText(sourceText);
|
||||
var root = tree.GetRoot().NormalizeWhitespace();
|
||||
var ret = root.ToFullString();
|
||||
context.AddSource($"{builder.GetFileName()}.g.cs", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
internal sealed class VariableSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public const string GeneratorVariableAttributeTypeName = "ThingsGateway.Foundation.GeneratorVariableAttribute";
|
||||
public const string VariableRuntimeAttributeTypeName = "ThingsGateway.Foundation.VariableRuntimeAttribute";
|
||||
|
||||
/// <summary>
|
||||
/// 接口列表
|
||||
/// </summary>
|
||||
private readonly List<ClassDeclarationSyntax> m_classSyntaxList = new List<ClassDeclarationSyntax>();
|
||||
|
||||
/// <summary>
|
||||
/// 访问语法树
|
||||
/// </summary>
|
||||
/// <param name="syntaxNode"></param>
|
||||
void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is ClassDeclarationSyntax syntax)
|
||||
{
|
||||
m_classSyntaxList.Add(syntax);
|
||||
}
|
||||
}
|
||||
|
||||
public INamedTypeSymbol GeneratorVariableAttributeAttribute { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有插件符号
|
||||
/// </summary>
|
||||
/// <param name="compilation"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<INamedTypeSymbol> GetVariableObjectTypes(Compilation compilation)
|
||||
{
|
||||
GeneratorVariableAttributeAttribute = compilation.GetTypeByMetadataName(GeneratorVariableAttributeTypeName)!;
|
||||
if (GeneratorVariableAttributeAttribute == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (var classSyntax in m_classSyntaxList)
|
||||
{
|
||||
var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax);
|
||||
if (@class != null && IsVariableObject(@class))
|
||||
{
|
||||
yield return @class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为变量类
|
||||
/// </summary>
|
||||
/// <param name="class"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsVariableObject(INamedTypeSymbol @class)
|
||||
{
|
||||
if (GeneratorVariableAttributeAttribute is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@class.IsAbstract)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return HasAttribute(@class, GeneratorVariableAttributeAttribute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回是否声明指定的特性
|
||||
/// </summary>
|
||||
/// <param name="symbol"></param>
|
||||
/// <param name="attribute"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attribute)
|
||||
{
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
if (attrClass != null && (attrClass.AllInterfaces.Contains(attribute) || SymbolEqualityComparer.Default.Equals(attrClass, attribute)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,49 @@
|
||||
param($installPath, $toolsPath, $package, $project)
|
||||
|
||||
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Install the language agnostic analyzers.
|
||||
if (Test-Path $analyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# $project.Type gives the language name like (C# or VB.NET)
|
||||
$languageFolder = ""
|
||||
if($project.Type -eq "C#")
|
||||
{
|
||||
$languageFolder = "cs"
|
||||
}
|
||||
if($project.Type -eq "VB.NET")
|
||||
{
|
||||
$languageFolder = "vb"
|
||||
}
|
||||
if($languageFolder -eq "")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Install language specific analyzers.
|
||||
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
||||
if (Test-Path $languageAnalyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
param($installPath, $toolsPath, $package, $project)
|
||||
|
||||
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Uninstall the language agnostic analyzers.
|
||||
if (Test-Path $analyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# $project.Type gives the language name like (C# or VB.NET)
|
||||
$languageFolder = ""
|
||||
if($project.Type -eq "C#")
|
||||
{
|
||||
$languageFolder = "cs"
|
||||
}
|
||||
if($project.Type -eq "VB.NET")
|
||||
{
|
||||
$languageFolder = "vb"
|
||||
}
|
||||
if($languageFolder -eq "")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Uninstall language specific analyzers.
|
||||
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
||||
if (Test-Path $languageAnalyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
try
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
global using ThingsGateway.Foundation;
|
||||
|
||||
global using TouchSocket.Core;
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net9.0;net8.0;net6.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj">
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> none;</IncludeAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.CSScript\ThingsGateway.CSScript.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\ThingsGateway.Foundation.SourceGenerator\tools\*.ps1" PackagePath="tools" Pack="true" Visible="false" />
|
||||
<None Include="..\ThingsGateway.Foundation.SourceGenerator\bin\$(Configuration)\netstandard2.0\ThingsGateway.Foundation.SourceGenerator.dll" PackagePath="analyzers\dotnet\cs" Pack="true" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,245 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// VariableObject
|
||||
/// </summary>
|
||||
public abstract class VariableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 协议对象
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IProtocol Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// VariableRuntimePropertyDict
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, VariableRuntimeProperty>? VariableRuntimePropertyDict;
|
||||
|
||||
/// <summary>
|
||||
/// DeviceVariableSourceReads
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
protected List<VariableSourceClass>? DeviceVariableSourceReads;
|
||||
|
||||
/// <summary>
|
||||
/// MaxPack
|
||||
/// </summary>
|
||||
protected int MaxPack;
|
||||
|
||||
/// <summary>
|
||||
/// VariableObject
|
||||
/// </summary>
|
||||
public VariableObject(IProtocol protocol, int maxPack)
|
||||
{
|
||||
Protocol = protocol;
|
||||
MaxPack = maxPack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ReadTime
|
||||
/// </summary>
|
||||
public DateTime ReadTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// GetExpressionsValue
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="variableRuntimeProperty"></param>
|
||||
/// <returns></returns>
|
||||
public virtual JToken GetExpressionsValue(object value, VariableRuntimeProperty variableRuntimeProperty)
|
||||
{
|
||||
var jToken = JToken.FromObject(value);
|
||||
if (!string.IsNullOrEmpty(variableRuntimeProperty.Attribute.WriteExpressions))
|
||||
{
|
||||
object rawdata = jToken is JValue jValue ? jValue.Value : jToken is JArray jArray ? jArray : jToken.ToString();
|
||||
|
||||
object data = variableRuntimeProperty.Attribute.WriteExpressions.GetExpressionsResult(rawdata);
|
||||
jToken = JToken.FromObject(data);
|
||||
}
|
||||
|
||||
return jToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetBytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual byte[] GetBytes(Expression<Func<object>> accessor)
|
||||
{
|
||||
if (accessor.Body == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(accessor));
|
||||
}
|
||||
|
||||
var expression = accessor.Body;
|
||||
if (expression is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert && unaryExpression.Type == typeof(object))
|
||||
{
|
||||
expression = unaryExpression.Operand;
|
||||
}
|
||||
|
||||
if (expression is not MemberExpression memberExpression)
|
||||
{
|
||||
throw new ArgumentException("Can only access properties");
|
||||
}
|
||||
// 从字典中获取与属性对应的变量信息
|
||||
if (!VariableRuntimePropertyDict.TryGetValue(memberExpression.Member.Name, out var variable))
|
||||
{
|
||||
throw new KeyNotFoundException($"Variable for {memberExpression.Member.Name} not found.");
|
||||
}
|
||||
|
||||
var func = accessor.Compile();
|
||||
|
||||
return variable.VariableClass.ThingsGatewayBitConverter.GetBytesFormData(GetExpressionsValue(func(), variable), variable.VariableClass.DataType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetVariableClass
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual List<VariableClass> GetVariableClass()
|
||||
{
|
||||
VariableRuntimePropertyDict ??= VariableObjectHelper.GetVariableRuntimePropertyDict(GetType());
|
||||
List<VariableClass> variableClasss = new();
|
||||
foreach (var pair in VariableRuntimePropertyDict)
|
||||
{
|
||||
var dataType = pair.Value.Attribute.DataType == DataTypeEnum.Object ? Type.GetTypeCode(pair.Value.Property.PropertyType.IsArray ? pair.Value.Property.PropertyType.GetElementType() : pair.Value.Property.PropertyType).GetDataType() : pair.Value.Attribute.DataType;
|
||||
VariableClass variableClass = new VariableClass()
|
||||
{
|
||||
DataType = dataType,
|
||||
RegisterAddress = pair.Value.Attribute.RegisterAddress,
|
||||
IntervalTime = "1000",
|
||||
};
|
||||
pair.Value.VariableClass = variableClass;
|
||||
variableClasss.Add(variableClass);
|
||||
}
|
||||
|
||||
return variableClasss;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性
|
||||
/// </summary>
|
||||
public virtual async ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetVariableSources();
|
||||
//连读
|
||||
foreach (var item in DeviceVariableSourceReads)
|
||||
{
|
||||
var result = await Protocol.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var result1 = item.VariableRunTimes.PraseStructContent(Protocol, result.Content, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.LastErrorMessage = result.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
return new OperResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue();
|
||||
return OperResult.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结果反射赋值
|
||||
/// </summary>
|
||||
public virtual void SetValue()
|
||||
{
|
||||
//结果反射赋值
|
||||
foreach (var pair in VariableRuntimePropertyDict)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pair.Value.Attribute.ReadExpressions))
|
||||
{
|
||||
var data = pair.Value.Attribute.ReadExpressions.GetExpressionsResult(pair.Value.VariableClass.Value);
|
||||
pair.Value.Property.SetValue(this, data.ChangeType(pair.Value.Property.PropertyType));
|
||||
}
|
||||
else
|
||||
{
|
||||
pair.Value.Property.SetValue(this, pair.Value.VariableClass.Value.ChangeType(pair.Value.Property.PropertyType));
|
||||
}
|
||||
}
|
||||
|
||||
ReadTime = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入值到设备中
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param>
|
||||
/// <param name="value">写入值</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
public virtual async ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetVariableSources();
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
return new OperResult($"PropertyName cannot be null or empty.");
|
||||
}
|
||||
|
||||
if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty))
|
||||
{
|
||||
return new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute");
|
||||
}
|
||||
|
||||
JToken jToken = GetExpressionsValue(value, variableRuntimeProperty);
|
||||
|
||||
var result = await Protocol.WriteAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetVariableSources
|
||||
/// </summary>
|
||||
protected virtual void GetVariableSources()
|
||||
{
|
||||
if (DeviceVariableSourceReads == null)
|
||||
{
|
||||
List<VariableClass> variableClasss = GetVariableClass();
|
||||
DeviceVariableSourceReads = Protocol.LoadSourceRead<VariableSourceClass>(variableClasss, MaxPack, "1000");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// VariableObjectHelper
|
||||
/// </summary>
|
||||
public static class VariableObjectHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// GetVariableRuntimePropertyDict
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, VariableRuntimeProperty> GetVariableRuntimePropertyDict(Type type)
|
||||
{
|
||||
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var dictionary = new Dictionary<string, VariableRuntimeProperty>();
|
||||
foreach (var propertyInfo in properties)
|
||||
{
|
||||
VariableRuntimeAttribute variableRuntimeAttribute = propertyInfo.GetCustomAttribute<VariableRuntimeAttribute>();
|
||||
if (variableRuntimeAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
dictionary.Add(propertyInfo.Name, new VariableRuntimeProperty(variableRuntimeAttribute, propertyInfo));
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 变量特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class VariableRuntimeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据类型,默认不填时会使用属性的Type
|
||||
/// </summary>
|
||||
public DataTypeEnum DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读取表达式
|
||||
/// </summary>
|
||||
public string? ReadExpressions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 寄存器地址
|
||||
/// </summary>
|
||||
public string? RegisterAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入表达式
|
||||
/// </summary>
|
||||
public string? WriteExpressions { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// VariableRuntimeProperty
|
||||
/// </summary>
|
||||
public class VariableRuntimeProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// VariableRuntimeProperty
|
||||
/// </summary>
|
||||
/// <param name="attribute"></param>
|
||||
/// <param name="property"></param>
|
||||
public VariableRuntimeProperty(VariableRuntimeAttribute attribute, PropertyInfo property)
|
||||
{
|
||||
Attribute = attribute;
|
||||
Property = property;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute
|
||||
/// </summary>
|
||||
public VariableRuntimeAttribute Attribute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Property
|
||||
/// </summary>
|
||||
public PropertyInfo Property { get; }
|
||||
|
||||
/// <summary>
|
||||
/// VariableClass
|
||||
/// </summary>
|
||||
public VariableClass VariableClass { get; set; }
|
||||
}
|
||||
135
src/Foundation/ThingsGateway.Foundation/Channel/ChannelData.cs
Normal file
135
src/Foundation/ThingsGateway.Foundation/Channel/ChannelData.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.IO.Ports;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ChannelData
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public long Id { get; set; } = IncrementCount.GetCurrentValue();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ChannelTypeEnum ChannelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
|
||||
/// </summary>
|
||||
public string? RemoteUrl { get; set; } = "127.0.0.1:502";
|
||||
|
||||
/// <summary>
|
||||
/// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
|
||||
/// </summary>
|
||||
public string? BindUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// COM
|
||||
/// </summary>
|
||||
public string? PortName { get; set; } = "COM1";
|
||||
|
||||
/// <summary>
|
||||
/// 波特率
|
||||
/// </summary>
|
||||
public int? BaudRate { get; set; } = 9600;
|
||||
|
||||
/// <summary>
|
||||
/// 数据位
|
||||
/// </summary>
|
||||
public int? DataBits { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// 校验位
|
||||
/// </summary>
|
||||
public Parity? Parity { get; set; } = System.IO.Ports.Parity.None;
|
||||
|
||||
/// <summary>
|
||||
/// 停止位
|
||||
/// </summary>
|
||||
public StopBits? StopBits { get; set; } = System.IO.Ports.StopBits.One;
|
||||
|
||||
/// <summary>
|
||||
/// DtrEnable
|
||||
/// </summary>
|
||||
public bool? DtrEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// RtsEnable
|
||||
/// </summary>
|
||||
public bool? RtsEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// TouchSocketConfig
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public TouchSocketConfig TouchSocketConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Channel
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public IChannel Channel;
|
||||
|
||||
private static IncrementCount IncrementCount = new(long.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// 创建通道
|
||||
/// </summary>
|
||||
/// <param name="channelData"></param>
|
||||
public static async Task CreateChannelAsync(ChannelData channelData)
|
||||
{
|
||||
if (channelData.Channel != null)
|
||||
{
|
||||
channelData.Channel.Close();
|
||||
channelData.Channel.SafeDispose();
|
||||
}
|
||||
channelData.TouchSocketConfig?.Dispose();
|
||||
channelData.TouchSocketConfig = new TouchSocket.Core.TouchSocketConfig();
|
||||
var logMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
var logger = TextFileLogger.CreateTextLogger(channelData.Id.GetDebugLogPath());
|
||||
logger.LogLevel = LogLevel.Trace;
|
||||
logMessage.AddLogger(logger);
|
||||
channelData.TouchSocketConfig.ConfigureContainer(a => a.RegisterSingleton<ILog>(logMessage));
|
||||
|
||||
switch (channelData.ChannelType)
|
||||
{
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
channelData.Channel = await channelData.TouchSocketConfig.GetTcpClientWithIPHostAsync(channelData.RemoteUrl, channelData.BindUrl).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case ChannelTypeEnum.TcpService:
|
||||
channelData.Channel = await channelData.TouchSocketConfig.GetTcpServiceWithBindIPHostAsync(channelData.BindUrl).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
channelData.Channel = await channelData.TouchSocketConfig.GetSerialPortWithOptionAsync(channelData.Map<SerialPortOption>()).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
channelData.Channel = await channelData.TouchSocketConfig.GetUdpSessionWithIPHostAsync(channelData.RemoteUrl, channelData.BindUrl).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通道扩展
|
||||
/// </summary>
|
||||
public static class ChannelConfigExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发通道接收事件
|
||||
/// </summary>
|
||||
/// <param name="clientChannel">通道</param>
|
||||
/// <param name="e">接收数据</param>
|
||||
/// <param name="funcs">事件</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
||||
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
||||
|
||||
if (funcs.Count > 0)
|
||||
{
|
||||
foreach (var func in funcs)
|
||||
{
|
||||
await func.Invoke(clientChannel, e).ConfigureAwait(false);
|
||||
if (e.Handled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发通道事件
|
||||
/// </summary>
|
||||
/// <param name="clientChannel">通道</param>
|
||||
/// <param name="funcs">事件</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
|
||||
{
|
||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||
funcs.ThrowIfNull(nameof(ChannelEventHandler));
|
||||
|
||||
if (funcs.Count > 0)
|
||||
{
|
||||
foreach (var func in funcs)
|
||||
{
|
||||
var handled = await func.Invoke(clientChannel).ConfigureAwait(false);
|
||||
if (handled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的通道。传入通道类型,远程服务端地址,绑定地址,串口配置信息
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="channelType">通道类型</param>
|
||||
/// <param name="remoteUrl">远端IP端口配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <param name="serialPortOption">串口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static async ValueTask<IChannel?> GetChannelAsync(this TouchSocketConfig config, ChannelTypeEnum channelType, string? remoteUrl = default, string? bindUrl = default, SerialPortOption? serialPortOption = default)
|
||||
{
|
||||
config.ThrowIfNull(nameof(TouchSocketConfig));
|
||||
channelType.ThrowIfNull(nameof(ChannelTypeEnum));
|
||||
|
||||
|
||||
switch (channelType)
|
||||
{
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
remoteUrl.ThrowIfNull(nameof(IPHost));
|
||||
return await config.GetTcpClientWithIPHostAsync(remoteUrl, bindUrl).ConfigureAwait(false);
|
||||
|
||||
case ChannelTypeEnum.TcpService:
|
||||
bindUrl.ThrowIfNull(nameof(IPHost));
|
||||
return await config.GetTcpServiceWithBindIPHostAsync(bindUrl).ConfigureAwait(false);
|
||||
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
|
||||
return await config.GetSerialPortWithOptionAsync(serialPortOption).ConfigureAwait(false);
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
if (string.IsNullOrEmpty(remoteUrl) && string.IsNullOrEmpty(bindUrl))
|
||||
throw new ArgumentNullException(nameof(IPHost));
|
||||
return await config.GetUdpSessionWithIPHostAsync(remoteUrl, bindUrl).ConfigureAwait(false);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的串口通道。传入串口配置信息
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="serialPortOption">串口配置</param>
|
||||
/// <returns></returns>
|
||||
public static async ValueTask<SerialPortChannel> GetSerialPortWithOptionAsync(this TouchSocketConfig config, SerialPortOption serialPortOption)
|
||||
{
|
||||
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
|
||||
config.SetSerialPortOption(serialPortOption);
|
||||
|
||||
//载入配置
|
||||
SerialPortChannel serialPortChannel = new SerialPortChannel();
|
||||
await serialPortChannel.SetupAsync(config).ConfigureAwait(false);
|
||||
|
||||
return serialPortChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的Tcp客户端通道。传入远程服务端地址和绑定地址
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="remoteUrl">远端IP端口配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static async ValueTask<TcpClientChannel> GetTcpClientWithIPHostAsync(this TouchSocketConfig config, string remoteUrl, string? bindUrl = default)
|
||||
{
|
||||
remoteUrl.ThrowIfNull(nameof(IPHost));
|
||||
config.SetRemoteIPHost(remoteUrl);
|
||||
if (!string.IsNullOrEmpty(bindUrl))
|
||||
config.SetBindIPHost(bindUrl);
|
||||
|
||||
//载入配置
|
||||
TcpClientChannel tcpClientChannel = new TcpClientChannel();
|
||||
await tcpClientChannel.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpClientChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的Tcp服务会话通道。传入远程服务端地址和绑定地址
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static async ValueTask<TcpServiceChannel> GetTcpServiceWithBindIPHostAsync(this TouchSocketConfig config, string bindUrl)
|
||||
{
|
||||
bindUrl.ThrowIfNull(nameof(IPHost));
|
||||
|
||||
var urls = bindUrl.SplitStringBySemicolon();
|
||||
config.SetListenIPHosts(IPHost.ParseIPHosts(urls));
|
||||
//载入配置
|
||||
TcpServiceChannel tcpServiceChannel = new TcpServiceChannel();
|
||||
await tcpServiceChannel.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpServiceChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的Udp会话通道。传入远程服务端地址和绑定地址
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="remoteUrl">远端IP端口配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static async ValueTask<UdpSessionChannel> GetUdpSessionWithIPHostAsync(this TouchSocketConfig config, string? remoteUrl, string? bindUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(remoteUrl) && string.IsNullOrEmpty(bindUrl))
|
||||
throw new ArgumentNullException(nameof(IPHost));
|
||||
|
||||
if (!string.IsNullOrEmpty(remoteUrl))
|
||||
config.SetRemoteIPHost(remoteUrl);
|
||||
|
||||
if (!string.IsNullOrEmpty(bindUrl))
|
||||
config.SetBindIPHost(bindUrl);
|
||||
else
|
||||
config.SetBindIPHost(new IPHost(0));
|
||||
|
||||
//载入配置
|
||||
UdpSessionChannel udpSessionChannel = new UdpSessionChannel();
|
||||
#if NET6_0_OR_GREATER
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
config.UseUdpConnReset();
|
||||
}
|
||||
#endif
|
||||
await udpSessionChannel.SetupAsync(config).ConfigureAwait(false);
|
||||
return udpSessionChannel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 能对适配器做配置的客户端
|
||||
/// </summary>
|
||||
public interface IAdapterObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置数据处理适配器
|
||||
/// </summary>
|
||||
/// <param name="adapter">适配器</param>
|
||||
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
|
||||
}
|
||||
99
src/Foundation/ThingsGateway.Foundation/Channel/IChannel.cs
Normal file
99
src/Foundation/ThingsGateway.Foundation/Channel/IChannel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 通道管理
|
||||
/// </summary>
|
||||
public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 接收数据事件
|
||||
/// </summary>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道类型
|
||||
/// </summary>
|
||||
public ChannelTypeEnum ChannelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道下的所有设备
|
||||
/// </summary>
|
||||
public ConcurrentList<IProtocol> Collects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Online
|
||||
/// </summary>
|
||||
public bool Online { get; }
|
||||
|
||||
/// <summary>
|
||||
/// MaxSign
|
||||
/// </summary>
|
||||
public int MaxSign { get; set; }
|
||||
/// <summary>
|
||||
/// 通道启动成功后
|
||||
/// </summary>
|
||||
public ChannelEventHandler Started { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道启动即将成功
|
||||
/// </summary>
|
||||
public ChannelEventHandler Starting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道停止
|
||||
/// </summary>
|
||||
public ChannelEventHandler Stoped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道停止前
|
||||
/// </summary>
|
||||
public ChannelEventHandler Stoping { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭客户端。
|
||||
/// </summary>
|
||||
/// <param name="msg">关闭消息</param>
|
||||
public void Close(string msg);
|
||||
|
||||
/// <summary>
|
||||
/// 启动
|
||||
/// </summary>
|
||||
/// <param name="millisecondsTimeout">最大等待时间</param>
|
||||
/// <param name="token">可取消令箭</param>
|
||||
/// <exception cref="TimeoutException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步连接
|
||||
/// </summary>
|
||||
/// <param name="millisecondsTimeout">最大等待时间</param>
|
||||
/// <param name="token">可取消令箭</param>
|
||||
/// <exception cref="TimeoutException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public Task ConnectAsync(int millisecondsTimeout = 3000, CancellationToken token = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收事件回调类
|
||||
/// </summary>
|
||||
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, Task>>
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 通道事件回调类
|
||||
/// </summary>
|
||||
public class ChannelEventHandler : List<Func<IClientChannel, Task<bool>>>
|
||||
{
|
||||
}
|
||||
@@ -8,18 +8,26 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 关系表种子数据
|
||||
/// 终端通道
|
||||
/// </summary>
|
||||
public class SysRelationSeedData : ISqlSugarEntitySeedData<SysRelation>
|
||||
public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOnlineClient, IAdapterObject
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<SysRelation> SeedData()
|
||||
{
|
||||
var data = SeedDataUtil.GetSeedData<SysRelation>(PathExtensions.CombinePathWithOs("SeedData", "Gateway", "seed_gateway_relation.json"));
|
||||
var assembly = GetType().Assembly;
|
||||
return SeedDataUtil.GetSeedDataByJson<SysRelation>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Gateway.seed_gateway_relation.json")).Concat(data);
|
||||
}
|
||||
/// <summary>
|
||||
/// 当前通道的数据处理适配器
|
||||
/// </summary>
|
||||
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道等待池
|
||||
/// </summary>
|
||||
WaitHandlePool<MessageBase> WaitHandlePool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 收发等待锁,对于大部分工业主从协议是必须的,一个通道一个实现
|
||||
/// </summary>
|
||||
WaitLock WaitLock { get; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[PluginOption(Singleton = true)]
|
||||
public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳16进制字符串
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
{
|
||||
var len = HeartbeatHexString.HexStringToBytes().Length;
|
||||
if (client is TcpSessionClientChannel socket && socket.Service is TcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
if (!socket.Id.StartsWith("ID="))
|
||||
{
|
||||
var id = $"ID={e.ByteBlock}";
|
||||
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
|
||||
{
|
||||
try
|
||||
{
|
||||
oldClient.TryShutdown();
|
||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||
oldClient.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
await socket.ResetIdAsync(id).ConfigureAwait(false);
|
||||
client.Logger?.Info(DefaultResource.Localizer["DtuConnected", id]);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
if (!socket.Service.ClientExists(socket.Id))
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.TryShutdown();
|
||||
await socket.CloseAsync().ConfigureAwait(false);
|
||||
socket.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
|
||||
{
|
||||
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
//回应心跳包
|
||||
await socket.SendAsync(e.ByteBlock.AsSegment()).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
if (socket.Logger?.LogLevel <= LogLevel.Trace)
|
||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||
}
|
||||
}
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
[PluginOption(Singleton = true)]
|
||||
internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin
|
||||
{
|
||||
public string DtuId { get; set; } = "DtuId";
|
||||
public string HeartbeatHexString { get; set; } = "HeartbeatHexString";
|
||||
public int HeartbeatTime { get; set; } = 3;
|
||||
|
||||
public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e)
|
||||
{
|
||||
if (client is ITcpSessionClient)
|
||||
{
|
||||
return;//此处可判断,如果为服务器,则不用使用心跳。
|
||||
}
|
||||
|
||||
if (DtuId.IsNullOrWhiteSpace()) return;
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
await tcpClient.SendAsync(DtuId.ToUTF8Bytes()).ConfigureAwait(false);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var failedCount = 0;
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(HeartbeatTime * 1000).ConfigureAwait(false);
|
||||
if (!client.Online)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await tcpClient.SendAsync(HeartbeatHexString.HexStringToBytes()).ConfigureAwait(false);
|
||||
failedCount = 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failedCount++;
|
||||
}
|
||||
if (failedCount > 3)
|
||||
{
|
||||
await client.CloseAsync("The automatic heartbeat has failed more than 3 times and has been disconnected.").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await e.InvokeNext().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
{
|
||||
if (client is ITcpSessionClient)
|
||||
{
|
||||
return;//此处可判断,如果为服务器,则不用使用心跳。
|
||||
}
|
||||
|
||||
if (DtuId.IsNullOrWhiteSpace()) return;
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
var len = HeartbeatHexString.HexStringToBytes().Length;
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public interface IDtu : ITcpService
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳检测(大写16进制字符串)
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认Dtu注册包,utf-8字符串
|
||||
/// </summary>
|
||||
public string DtuId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public interface IDtuClient
|
||||
{
|
||||
/// <summary>
|
||||
/// DtuId
|
||||
/// </summary>
|
||||
public string DtuId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 心跳内容
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 心跳时间
|
||||
/// </summary>
|
||||
public int HeartbeatTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public interface ITcpService
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端连接滑动过期时间(TCP服务通道时)
|
||||
/// </summary>
|
||||
public int CheckClearTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static class PluginUtil
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public static Action<IPluginManager> GetDtuClientPlugin(IDtuClient dtuClient)
|
||||
{
|
||||
Action<IPluginManager> action = a => { };
|
||||
|
||||
action += a =>
|
||||
{
|
||||
var plugin = a.Add<HeartbeatAndReceivePlugin>();
|
||||
plugin.HeartbeatHexString = dtuClient.HeartbeatHexString;
|
||||
plugin.DtuId = dtuClient.DtuId;
|
||||
plugin.HeartbeatTime = dtuClient.HeartbeatTime;
|
||||
};
|
||||
return action;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static Action<IPluginManager> GetDtuPlugin(IDtu dtu)
|
||||
{
|
||||
Action<IPluginManager> action = a => { };
|
||||
|
||||
action += a =>
|
||||
{
|
||||
a.UseCheckClear()
|
||||
.SetCheckClearType(CheckClearType.All)
|
||||
.SetTick(TimeSpan.FromSeconds(dtu.CheckClearTime))
|
||||
.SetOnClose((c, t) =>
|
||||
{
|
||||
c.TryShutdown();
|
||||
c.SafeClose($"{dtu.CheckClearTime}s Timeout");
|
||||
});
|
||||
};
|
||||
|
||||
action += a =>
|
||||
{
|
||||
var plugin = a.Add<DtuPlugin>();
|
||||
plugin.HeartbeatHexString = dtu.HeartbeatHexString;
|
||||
};
|
||||
return action;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static Action<IPluginManager> GetTcpServicePlugin(ITcpService tcpService)
|
||||
{
|
||||
Action<IPluginManager> action = a => { };
|
||||
|
||||
action += a =>
|
||||
{
|
||||
a.UseCheckClear()
|
||||
.SetCheckClearType(CheckClearType.All)
|
||||
.SetTick(TimeSpan.FromSeconds(tcpService.CheckClearTime))
|
||||
.SetOnClose((c, t) =>
|
||||
{
|
||||
c.TryShutdown();
|
||||
c.SafeClose($"{tcpService.CheckClearTime}s Timeout");
|
||||
});
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 串口通道
|
||||
/// </summary>
|
||||
public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SerialPortChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.SerialPort;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task CloseAsync(string msg)
|
||||
{
|
||||
if (Online)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closed{msg}");
|
||||
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync(token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
var port = Config?.GetValue(SerialPortConfigExtension.SerialPortOptionProperty);
|
||||
if (port != null)
|
||||
return $"{port.PortName}[{port.BaudRate},{port.DataBits},{port.StopBits},{port.Parity}]";
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await base.OnSerialClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
|
||||
|
||||
await base.OnSerialConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnSerialReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Tcp客户端通道
|
||||
/// </summary>
|
||||
public class TcpClientChannel : TcpClient, IClientChannel
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TcpClientChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpClient;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task CloseAsync(string msg)
|
||||
{
|
||||
if (Online)
|
||||
{
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closed{msg}");
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync(token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{IP}:{Port}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnTcpClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnTcpConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// TCP服务器
|
||||
/// </summary>
|
||||
/// <typeparam name="TClient"></typeparam>
|
||||
public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcpService<TClient> where TClient : TcpSessionClientChannel, new()
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 停止时是否发送ShutDown
|
||||
/// </summary>
|
||||
public bool ShutDownEnable { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ClearAsync()
|
||||
{
|
||||
foreach (var client in Clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ShutDownEnable)
|
||||
client.TryShutdown();
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task ClientDisposeAsync(string id)
|
||||
{
|
||||
if (this.TryGetClient(id, out var client))
|
||||
{
|
||||
if (ShutDownEnable)
|
||||
client.TryShutdown();
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override async Task StartAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (ServerState != ServerState.Running)
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
if (ServerState == ServerState.Running)
|
||||
{
|
||||
Logger?.Info($"{Monitors.FirstOrDefault()?.Option.IpHost}{DefaultResource.Localizer["ServiceStarted"]}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StopAsync()
|
||||
{
|
||||
if (Monitors.Any())
|
||||
{
|
||||
await ClearAsync().ConfigureAwait(false);
|
||||
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
if (!Monitors.Any())
|
||||
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
return Monitors.FirstOrDefault()?.Option?.IpHost.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosed(TClient socketClient, ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Closed");
|
||||
return base.OnTcpClosed(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosing(TClient socketClient, ClosingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Closing");
|
||||
return base.OnTcpClosing(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpConnected(TClient socketClient, ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Connected");
|
||||
return base.OnTcpConnected(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpConnecting(TClient socketClient, ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Connecting");
|
||||
return base.OnTcpConnecting(socketClient, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tcp服务器通道
|
||||
/// </summary>
|
||||
public class TcpServiceChannel : TcpServiceChannelBase<TcpSessionClientChannel>, IChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Online => ServerState == ServerState.Running;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return EasyTask.CompletedTask;
|
||||
|
||||
return StartAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override TcpSessionClientChannel NewClient()
|
||||
{
|
||||
var data = new TcpSessionClientChannel();
|
||||
data.WaitHandlePool.MaxSign = MaxSign;
|
||||
return data;
|
||||
}
|
||||
public int MaxSign { get; set; }
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(TcpSessionClientChannel socketClient, ClosingEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.OnTcpClosing(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosed(TcpSessionClientChannel socketClient, ClosedEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
await base.OnTcpClosed(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnected(TcpSessionClientChannel socketClient, ConnectedEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
await base.OnTcpConnected(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(TcpSessionClientChannel socketClient, ConnectingEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnTcpConnecting(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(TcpSessionClientChannel socketClient, ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
|
||||
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Tcp终端通道
|
||||
/// </summary>
|
||||
public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public TcpSessionClientChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task CloseAsync(string msg)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
return base.CloseAsync(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(int timeout, CancellationToken token) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task SetupAsync(TouchSocketConfig config)
|
||||
{
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task ResetIdAsync(string newId)
|
||||
{
|
||||
await base.ResetIdAsync(newId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{IP}:{Port}:{Id}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (DisposedValue) return;
|
||||
WaitHandlePool.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosed(ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
await base.OnTcpClosed(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(ClosingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.OnTcpClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnected(ConnectedEventArgs e)
|
||||
{
|
||||
//Logger?.Debug($"{ToString()}{FoundationConst.Connected}");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
|
||||
await base.OnTcpConnected(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
|
||||
await base.OnTcpConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Udp通道
|
||||
/// </summary>
|
||||
public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UdpSessionChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.UdpSession;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Online => ServerState == ServerState.Running;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await StartAsync().ConfigureAwait(false);
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
|
||||
SetAdapter(udpDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StartAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (ServerState != ServerState.Running)
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
if (ServerState == ServerState.Running)
|
||||
{
|
||||
Logger?.Info($"{Monitor.IPHost}{DefaultResource.Localizer["ServiceStarted"]}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StopAsync()
|
||||
{
|
||||
if (Monitor != null)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
if (Monitor == null)
|
||||
{
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
return RemoteIPHost?.ToString().Replace("tcp", "udp");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnUdpReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
127
src/Foundation/ThingsGateway.Foundation/Common/IncrementCount.cs
Normal file
127
src/Foundation/ThingsGateway.Foundation/Common/IncrementCount.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 自增数据类,用于自增数据,可以设置最大值,初始值,自增步长等。
|
||||
/// </summary>
|
||||
public sealed class IncrementCount : DisposableObject
|
||||
{
|
||||
private readonly WaitLock easyLock = new();
|
||||
private long current = 0;
|
||||
private long max = long.MaxValue;
|
||||
private long start = 0;
|
||||
|
||||
/// <inheritdoc cref="IncrementCount"/>
|
||||
public IncrementCount(long max, long start = 0, int tick = 1)
|
||||
{
|
||||
this.start = start;
|
||||
this.max = max;
|
||||
current = start;
|
||||
IncreaseTick = tick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自增步长
|
||||
/// </summary>
|
||||
public int IncreaseTick { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前的计数器的最大的设置值
|
||||
/// </summary>
|
||||
public long MaxValue => max;
|
||||
|
||||
/// <summary>
|
||||
/// 获取自增信息,获得数据之后,下一次获取将会自增,如果自增后大于最大值,则会重置为最小值,如果小于最小值,则会重置为最大值。
|
||||
/// </summary>
|
||||
public long GetCurrentValue()
|
||||
{
|
||||
easyLock.Wait();
|
||||
long current = this.current;
|
||||
this.current += IncreaseTick;
|
||||
if (this.current > max)
|
||||
{
|
||||
this.current = start;
|
||||
}
|
||||
else if (this.current < start)
|
||||
{
|
||||
this.current = max;
|
||||
}
|
||||
|
||||
easyLock.Release();
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前的值重置为初始值。
|
||||
/// </summary>
|
||||
public void ResetCurrentValue()
|
||||
{
|
||||
easyLock.Wait();
|
||||
current = start;
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前的值重置为指定值
|
||||
/// </summary>
|
||||
/// <param name="value">指定值</param>
|
||||
public void ResetCurrentValue(long value)
|
||||
{
|
||||
easyLock.Wait();
|
||||
current = value <= max ? value >= start ? value : start : max;
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前序号的最大值
|
||||
/// </summary>
|
||||
public void ResetMaxValue(long max)
|
||||
{
|
||||
easyLock.Wait();
|
||||
if (max > start)
|
||||
{
|
||||
if (max < current)
|
||||
{
|
||||
current = start;
|
||||
}
|
||||
|
||||
this.max = max;
|
||||
}
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前序号的初始值
|
||||
/// </summary>
|
||||
/// <param name="start">初始值</param>
|
||||
public void ResetStartValue(long start)
|
||||
{
|
||||
easyLock.Wait();
|
||||
if (start < max)
|
||||
{
|
||||
if (current < start)
|
||||
{
|
||||
current = start;
|
||||
}
|
||||
|
||||
this.start = start;
|
||||
}
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
easyLock.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
87
src/Foundation/ThingsGateway.Foundation/Common/TimeTick.cs
Normal file
87
src/Foundation/ThingsGateway.Foundation/Common/TimeTick.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 时间刻度器,最小时间间隔10毫秒
|
||||
/// </summary>
|
||||
public class TimeTick
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间间隔(毫秒)
|
||||
/// </summary>
|
||||
private readonly int intervalMilliseconds = 1000;
|
||||
private readonly Cron cron;
|
||||
/// <inheritdoc cref="TimeTick"/>
|
||||
public TimeTick(string delay)
|
||||
{
|
||||
if (int.TryParse(delay, out intervalMilliseconds))
|
||||
{
|
||||
if (intervalMilliseconds < 10)
|
||||
intervalMilliseconds = 10;
|
||||
LastTime = DateTime.Now.AddMilliseconds(-intervalMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
cron = new Cron(delay);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上次触发时间
|
||||
/// </summary>
|
||||
public DateTime LastTime { get; private set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// 是否触发时间刻度
|
||||
/// </summary>
|
||||
/// <param name="currentTime">当前时间</param>
|
||||
/// <returns>是否触发时间刻度</returns>
|
||||
public bool IsTickHappen(DateTime currentTime)
|
||||
{
|
||||
DateTime nextTime = DateTime.MinValue;
|
||||
if (cron == null)
|
||||
{
|
||||
nextTime = LastTime.AddMilliseconds(intervalMilliseconds);
|
||||
var diffMilliseconds = (currentTime - nextTime).TotalMilliseconds;
|
||||
if (diffMilliseconds < 0)
|
||||
return false;
|
||||
else if (diffMilliseconds * 2 < intervalMilliseconds)
|
||||
LastTime = nextTime;
|
||||
else
|
||||
LastTime = nextTime;//选择当前时间
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextTime = cron.GetNext(LastTime);
|
||||
if (currentTime >= nextTime)
|
||||
{
|
||||
LastTime = nextTime;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否到达设置的时间间隔
|
||||
/// </summary>
|
||||
/// <returns>是否到达设置的时间间隔</returns>
|
||||
public bool IsTickHappen() => IsTickHappen(DateTime.Now);
|
||||
}
|
||||
89
src/Foundation/ThingsGateway.Foundation/Common/WaitLock.cs
Normal file
89
src/Foundation/ThingsGateway.Foundation/Common/WaitLock.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// WaitLock,使用轻量级SemaphoreSlim锁,只允许一个并发量,并记录并发信息
|
||||
/// </summary>
|
||||
public sealed class WaitLock : DisposableObject
|
||||
{
|
||||
private readonly SemaphoreSlim m_waiterLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock(bool initialState = true)
|
||||
{
|
||||
if (!initialState)
|
||||
m_waiterLock.Wait();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
~WaitLock()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前锁是否在等待当中
|
||||
/// </summary>
|
||||
public bool IsWaitting => m_waiterLock.CurrentCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// 离开锁
|
||||
/// </summary>
|
||||
public void Release()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (IsWaitting)
|
||||
m_waiterLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public void Wait(CancellationToken cancellationToken = default)
|
||||
{
|
||||
m_waiterLock.Wait(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public bool Wait(TimeSpan timeSpan, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = m_waiterLock.Wait(timeSpan, cancellationToken);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public Task WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return m_waiterLock.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public Task<bool> WaitAsync(TimeSpan timeSpan, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return m_waiterLock.WaitAsync(timeSpan, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
m_waiterLock.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Json字符串转到对应类
|
||||
/// </summary>
|
||||
public class JsonStringToClassSerializerFormatter<TState> : ISerializerFormatter<string, TState>
|
||||
{
|
||||
/// <summary>
|
||||
/// JsonSettings
|
||||
/// </summary>
|
||||
public JsonSerializerSettings JsonSettings { get; set; } = new JsonSerializerSettings();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int Order { get; set; } = -99;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDeserialize(TState state, in string source, Type targetType, out object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
target = JsonConvert.DeserializeObject(source, targetType, JsonSettings)!;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TrySerialize(TState state, in object target, out string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
source = JsonConvert.SerializeObject(target, JsonSettings);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
source = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// String值转换为基础类型。
|
||||
/// </summary>
|
||||
public class StringToClassConverter<TState> : ISerializerFormatter<string, TState>
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int Order { get; set; } = -100;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDeserialize(TState state, in string source, Type targetType, out object target)
|
||||
{
|
||||
return targetType.GetTypeValue(source, out target!);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TrySerialize(TState state, in object target, out string source)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
var targetType = target.GetType();
|
||||
return targetType.GetTypeStringValue(target, out source!);
|
||||
}
|
||||
else
|
||||
{
|
||||
source = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class StringToEncodingConverter : ISerializerFormatter<string, object>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDeserialize(object state, in string source, Type targetType, out object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
target = Encoding.Default;
|
||||
if (targetType == typeof(Encoding))
|
||||
{
|
||||
target = Encoding.GetEncoding(source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TrySerialize(object state, in object target, out string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (target?.GetType() == typeof(Encoding))
|
||||
{
|
||||
source = (target as Encoding).WebName;
|
||||
return true;
|
||||
}
|
||||
source = target.ToJsonString();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
source = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// String类型数据转换器
|
||||
/// </summary>
|
||||
public class ThingsGatewayStringConverter : StringSerializerConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认实例
|
||||
/// </summary>
|
||||
public static ThingsGatewayStringConverter Default = new ThingsGatewayStringConverter();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public ThingsGatewayStringConverter(params ISerializerFormatter<string, object>[] converters) : base(converters)
|
||||
{
|
||||
Add(new StringToClassConverter<object>());
|
||||
Add(new JsonStringToClassSerializerFormatter<object>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 采集返回消息
|
||||
/// </summary>
|
||||
public interface IResultMessage : IOperResult, IRequestInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据体长度
|
||||
/// </summary>
|
||||
int BodyLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析的字节信息
|
||||
/// </summary>
|
||||
byte[] Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息头的指令长度,不固定时返回0
|
||||
/// </summary>
|
||||
int HeaderLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
|
||||
/// </summary>
|
||||
int Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当收到数据,由框架封送有效载荷数据。
|
||||
/// 此时流位置为<see cref="HeaderLength"/>
|
||||
/// <para>但是如果是因为数据错误,则需要修改<see cref="ByteBlock.Position"/>到正确位置,如果都不正确,则设置<see cref="ByteBlock.Position"/>等于<see cref="ByteBlock.Length"/></para>
|
||||
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功有效</returns>
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
|
||||
/// <para>如果返回false,意味着放弃本次解析的所有数据,包括已经解析完成的Header</para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功的结果</returns>
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
|
||||
/// </summary>
|
||||
void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
|
||||
{
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user