Compare commits
3 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5842a583c7 | |
|
|
d7035466ae | |
|
|
dd704f75f1 |
318
.editorconfig
318
.editorconfig
|
|
@ -3,318 +3,10 @@ root = true
|
|||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = tab
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 120
|
||||
tab_width = 4
|
||||
trim_trailing_whitespace = true
|
||||
ij_continuation_indent_size = 4
|
||||
ij_formatter_off_tag = @formatter:off
|
||||
ij_formatter_on_tag = @formatter:on
|
||||
ij_formatter_tags_enabled = true
|
||||
ij_smart_tabs = false
|
||||
ij_visual_guides =
|
||||
ij_wrap_on_typing = true
|
||||
|
||||
[*.java]
|
||||
ij_wrap_on_typing = false
|
||||
ij_java_align_consecutive_assignments = false
|
||||
ij_java_align_consecutive_variable_declarations = false
|
||||
ij_java_align_group_field_declarations = false
|
||||
ij_java_align_multiline_annotation_parameters = false
|
||||
ij_java_align_multiline_array_initializer_expression = false
|
||||
ij_java_align_multiline_assignment = false
|
||||
ij_java_align_multiline_binary_operation = false
|
||||
ij_java_align_multiline_chained_methods = true
|
||||
ij_java_align_multiline_deconstruction_list_components = true
|
||||
ij_java_align_multiline_extends_list = false
|
||||
ij_java_align_multiline_for = true
|
||||
ij_java_align_multiline_method_parentheses = false
|
||||
ij_java_align_multiline_parameters = true
|
||||
ij_java_align_multiline_parameters_in_calls = true
|
||||
ij_java_align_multiline_parenthesized_expression = false
|
||||
ij_java_align_multiline_records = true
|
||||
ij_java_align_multiline_resources = true
|
||||
ij_java_align_multiline_ternary_operation = false
|
||||
ij_java_align_multiline_text_blocks = false
|
||||
ij_java_align_multiline_throws_list = false
|
||||
ij_java_align_subsequent_simple_methods = false
|
||||
ij_java_align_throws_keyword = false
|
||||
ij_java_align_types_in_multi_catch = true
|
||||
ij_java_annotation_parameter_wrap = off
|
||||
ij_java_array_initializer_new_line_after_left_brace = false
|
||||
ij_java_array_initializer_right_brace_on_new_line = false
|
||||
ij_java_array_initializer_wrap = off
|
||||
ij_java_assert_statement_colon_on_next_line = false
|
||||
ij_java_assert_statement_wrap = off
|
||||
ij_java_assignment_wrap = off
|
||||
ij_java_binary_operation_sign_on_next_line = false
|
||||
ij_java_binary_operation_wrap = off
|
||||
ij_java_blank_lines_after_anonymous_class_header = 0
|
||||
ij_java_blank_lines_after_class_header = 0
|
||||
ij_java_blank_lines_after_imports = 1
|
||||
ij_java_blank_lines_after_package = 1
|
||||
ij_java_blank_lines_around_class = 1
|
||||
ij_java_blank_lines_around_field = 0
|
||||
ij_java_blank_lines_around_field_in_interface = 0
|
||||
ij_java_blank_lines_around_initializer = 1
|
||||
ij_java_blank_lines_around_method = 1
|
||||
ij_java_blank_lines_around_method_in_interface = 1
|
||||
ij_java_blank_lines_before_class_end = 0
|
||||
ij_java_blank_lines_before_imports = 1
|
||||
ij_java_blank_lines_before_method_body = 0
|
||||
ij_java_blank_lines_before_package = 0
|
||||
ij_java_block_brace_style = end_of_line
|
||||
ij_java_block_comment_add_space = false
|
||||
ij_java_block_comment_at_first_column = true
|
||||
ij_java_builder_methods =
|
||||
ij_java_call_parameters_new_line_after_left_paren = false
|
||||
ij_java_call_parameters_right_paren_on_new_line = false
|
||||
ij_java_call_parameters_wrap = off
|
||||
ij_java_case_statement_on_separate_line = true
|
||||
ij_java_catch_on_new_line = false
|
||||
ij_java_class_annotation_wrap = split_into_lines
|
||||
ij_java_class_brace_style = end_of_line
|
||||
ij_java_class_count_to_use_import_on_demand = 5
|
||||
ij_java_class_names_in_javadoc = 1
|
||||
ij_java_deconstruction_list_wrap = normal
|
||||
ij_java_do_not_indent_top_level_class_members = false
|
||||
ij_java_do_not_wrap_after_single_annotation = false
|
||||
ij_java_do_not_wrap_after_single_annotation_in_parameter = false
|
||||
ij_java_do_while_brace_force = never
|
||||
ij_java_doc_add_blank_line_after_description = true
|
||||
ij_java_doc_add_blank_line_after_param_comments = false
|
||||
ij_java_doc_add_blank_line_after_return = false
|
||||
ij_java_doc_add_p_tag_on_empty_lines = true
|
||||
ij_java_doc_align_exception_comments = true
|
||||
ij_java_doc_align_param_comments = true
|
||||
ij_java_doc_do_not_wrap_if_one_line = false
|
||||
ij_java_doc_enable_formatting = true
|
||||
ij_java_doc_enable_leading_asterisks = true
|
||||
ij_java_doc_indent_on_continuation = false
|
||||
ij_java_doc_keep_empty_lines = true
|
||||
ij_java_doc_keep_empty_parameter_tag = true
|
||||
ij_java_doc_keep_empty_return_tag = true
|
||||
ij_java_doc_keep_empty_throws_tag = true
|
||||
ij_java_doc_keep_invalid_tags = true
|
||||
ij_java_doc_param_description_on_new_line = false
|
||||
ij_java_doc_preserve_line_breaks = false
|
||||
ij_java_doc_use_throws_not_exception_tag = true
|
||||
ij_java_else_on_new_line = false
|
||||
ij_java_entity_dd_prefix =
|
||||
ij_java_entity_dd_suffix = EJB
|
||||
ij_java_entity_eb_prefix =
|
||||
ij_java_entity_eb_suffix = Bean
|
||||
ij_java_entity_hi_prefix =
|
||||
ij_java_entity_hi_suffix = Home
|
||||
ij_java_entity_lhi_prefix = Local
|
||||
ij_java_entity_lhi_suffix = Home
|
||||
ij_java_entity_li_prefix = Local
|
||||
ij_java_entity_li_suffix =
|
||||
ij_java_entity_pk_class = java.lang.String
|
||||
ij_java_entity_ri_prefix =
|
||||
ij_java_entity_ri_suffix =
|
||||
ij_java_entity_vo_prefix =
|
||||
ij_java_entity_vo_suffix = VO
|
||||
ij_java_enum_constants_wrap = off
|
||||
ij_java_extends_keyword_wrap = off
|
||||
ij_java_extends_list_wrap = off
|
||||
ij_java_field_annotation_wrap = split_into_lines
|
||||
ij_java_field_name_prefix =
|
||||
ij_java_field_name_suffix =
|
||||
ij_java_filter_class_prefix =
|
||||
ij_java_filter_class_suffix =
|
||||
ij_java_filter_dd_prefix =
|
||||
ij_java_filter_dd_suffix =
|
||||
ij_java_finally_on_new_line = false
|
||||
ij_java_for_brace_force = never
|
||||
ij_java_for_statement_new_line_after_left_paren = false
|
||||
ij_java_for_statement_right_paren_on_new_line = false
|
||||
ij_java_for_statement_wrap = off
|
||||
ij_java_generate_final_locals = false
|
||||
ij_java_generate_final_parameters = false
|
||||
ij_java_if_brace_force = never
|
||||
ij_java_imports_layout = *, |, javax.**, java.**, |, $*
|
||||
ij_java_indent_case_from_switch = true
|
||||
ij_java_insert_inner_class_imports = false
|
||||
ij_java_insert_override_annotation = true
|
||||
ij_java_keep_blank_lines_before_right_brace = 2
|
||||
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
|
||||
ij_java_keep_blank_lines_in_code = 2
|
||||
ij_java_keep_blank_lines_in_declarations = 2
|
||||
ij_java_keep_builder_methods_indents = false
|
||||
ij_java_keep_control_statement_in_one_line = true
|
||||
ij_java_keep_first_column_comment = true
|
||||
ij_java_keep_indents_on_empty_lines = false
|
||||
ij_java_keep_line_breaks = true
|
||||
ij_java_keep_multiple_expressions_in_one_line = false
|
||||
ij_java_keep_simple_blocks_in_one_line = false
|
||||
ij_java_keep_simple_classes_in_one_line = false
|
||||
ij_java_keep_simple_lambdas_in_one_line = false
|
||||
ij_java_keep_simple_methods_in_one_line = false
|
||||
ij_java_label_indent_absolute = false
|
||||
ij_java_label_indent_size = 0
|
||||
ij_java_lambda_brace_style = end_of_line
|
||||
ij_java_layout_static_imports_separately = true
|
||||
ij_java_line_comment_add_space = false
|
||||
ij_java_line_comment_add_space_on_reformat = false
|
||||
ij_java_line_comment_at_first_column = true
|
||||
ij_java_listener_class_prefix =
|
||||
ij_java_listener_class_suffix =
|
||||
ij_java_local_variable_name_prefix =
|
||||
ij_java_local_variable_name_suffix =
|
||||
ij_java_message_dd_prefix =
|
||||
ij_java_message_dd_suffix = EJB
|
||||
ij_java_message_eb_prefix =
|
||||
ij_java_message_eb_suffix = Bean
|
||||
ij_java_method_annotation_wrap = split_into_lines
|
||||
ij_java_method_brace_style = end_of_line
|
||||
ij_java_method_call_chain_wrap = on_every_item
|
||||
ij_java_method_parameters_new_line_after_left_paren = false
|
||||
ij_java_method_parameters_right_paren_on_new_line = false
|
||||
ij_java_method_parameters_wrap = off
|
||||
ij_java_modifier_list_wrap = false
|
||||
ij_java_multi_catch_types_wrap = normal
|
||||
ij_java_names_count_to_use_import_on_demand = 3
|
||||
ij_java_new_line_after_lparen_in_annotation = false
|
||||
ij_java_new_line_after_lparen_in_deconstruction_pattern = true
|
||||
ij_java_new_line_after_lparen_in_record_header = false
|
||||
ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.*
|
||||
ij_java_parameter_annotation_wrap = off
|
||||
ij_java_parameter_name_prefix =
|
||||
ij_java_parameter_name_suffix =
|
||||
ij_java_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_java_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_java_place_assignment_sign_on_next_line = false
|
||||
ij_java_prefer_longer_names = true
|
||||
ij_java_prefer_parameters_wrap = false
|
||||
ij_java_record_components_wrap = normal
|
||||
ij_java_repeat_synchronized = true
|
||||
ij_java_replace_instanceof_and_cast = false
|
||||
ij_java_replace_null_check = true
|
||||
ij_java_replace_sum_lambda_with_method_ref = true
|
||||
ij_java_resource_list_new_line_after_left_paren = false
|
||||
ij_java_resource_list_right_paren_on_new_line = false
|
||||
ij_java_resource_list_wrap = off
|
||||
ij_java_rparen_on_new_line_in_annotation = false
|
||||
ij_java_rparen_on_new_line_in_deconstruction_pattern = true
|
||||
ij_java_rparen_on_new_line_in_record_header = false
|
||||
ij_java_servlet_class_prefix =
|
||||
ij_java_servlet_class_suffix =
|
||||
ij_java_servlet_dd_prefix =
|
||||
ij_java_servlet_dd_suffix =
|
||||
ij_java_session_dd_prefix =
|
||||
ij_java_session_dd_suffix = EJB
|
||||
ij_java_session_eb_prefix =
|
||||
ij_java_session_eb_suffix = Bean
|
||||
ij_java_session_hi_prefix =
|
||||
ij_java_session_hi_suffix = Home
|
||||
ij_java_session_lhi_prefix = Local
|
||||
ij_java_session_lhi_suffix = Home
|
||||
ij_java_session_li_prefix = Local
|
||||
ij_java_session_li_suffix =
|
||||
ij_java_session_ri_prefix =
|
||||
ij_java_session_ri_suffix =
|
||||
ij_java_session_si_prefix =
|
||||
ij_java_session_si_suffix = Service
|
||||
ij_java_space_after_closing_angle_bracket_in_type_argument = false
|
||||
ij_java_space_after_colon = true
|
||||
ij_java_space_after_comma = true
|
||||
ij_java_space_after_comma_in_type_arguments = true
|
||||
ij_java_space_after_for_semicolon = true
|
||||
ij_java_space_after_quest = true
|
||||
ij_java_space_after_type_cast = true
|
||||
ij_java_space_before_annotation_array_initializer_left_brace = false
|
||||
ij_java_space_before_annotation_parameter_list = false
|
||||
ij_java_space_before_array_initializer_left_brace = false
|
||||
ij_java_space_before_catch_keyword = true
|
||||
ij_java_space_before_catch_left_brace = true
|
||||
ij_java_space_before_catch_parentheses = true
|
||||
ij_java_space_before_class_left_brace = true
|
||||
ij_java_space_before_colon = true
|
||||
ij_java_space_before_colon_in_foreach = true
|
||||
ij_java_space_before_comma = false
|
||||
ij_java_space_before_deconstruction_list = false
|
||||
ij_java_space_before_do_left_brace = true
|
||||
ij_java_space_before_else_keyword = true
|
||||
ij_java_space_before_else_left_brace = true
|
||||
ij_java_space_before_finally_keyword = true
|
||||
ij_java_space_before_finally_left_brace = true
|
||||
ij_java_space_before_for_left_brace = true
|
||||
ij_java_space_before_for_parentheses = true
|
||||
ij_java_space_before_for_semicolon = false
|
||||
ij_java_space_before_if_left_brace = true
|
||||
ij_java_space_before_if_parentheses = true
|
||||
ij_java_space_before_method_call_parentheses = false
|
||||
ij_java_space_before_method_left_brace = true
|
||||
ij_java_space_before_method_parentheses = false
|
||||
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
|
||||
ij_java_space_before_quest = true
|
||||
ij_java_space_before_switch_left_brace = true
|
||||
ij_java_space_before_switch_parentheses = true
|
||||
ij_java_space_before_synchronized_left_brace = true
|
||||
ij_java_space_before_synchronized_parentheses = true
|
||||
ij_java_space_before_try_left_brace = true
|
||||
ij_java_space_before_try_parentheses = true
|
||||
ij_java_space_before_type_parameter_list = false
|
||||
ij_java_space_before_while_keyword = true
|
||||
ij_java_space_before_while_left_brace = true
|
||||
ij_java_space_before_while_parentheses = true
|
||||
ij_java_space_inside_one_line_enum_braces = false
|
||||
ij_java_space_within_empty_array_initializer_braces = false
|
||||
ij_java_space_within_empty_method_call_parentheses = false
|
||||
ij_java_space_within_empty_method_parentheses = false
|
||||
ij_java_spaces_around_additive_operators = true
|
||||
ij_java_spaces_around_annotation_eq = true
|
||||
ij_java_spaces_around_assignment_operators = true
|
||||
ij_java_spaces_around_bitwise_operators = true
|
||||
ij_java_spaces_around_equality_operators = true
|
||||
ij_java_spaces_around_lambda_arrow = true
|
||||
ij_java_spaces_around_logical_operators = true
|
||||
ij_java_spaces_around_method_ref_dbl_colon = false
|
||||
ij_java_spaces_around_multiplicative_operators = true
|
||||
ij_java_spaces_around_relational_operators = true
|
||||
ij_java_spaces_around_shift_operators = true
|
||||
ij_java_spaces_around_type_bounds_in_type_parameters = true
|
||||
ij_java_spaces_around_unary_operator = false
|
||||
ij_java_spaces_within_angle_brackets = false
|
||||
ij_java_spaces_within_annotation_parentheses = false
|
||||
ij_java_spaces_within_array_initializer_braces = false
|
||||
ij_java_spaces_within_braces = false
|
||||
ij_java_spaces_within_brackets = false
|
||||
ij_java_spaces_within_cast_parentheses = false
|
||||
ij_java_spaces_within_catch_parentheses = false
|
||||
ij_java_spaces_within_deconstruction_list = false
|
||||
ij_java_spaces_within_for_parentheses = false
|
||||
ij_java_spaces_within_if_parentheses = false
|
||||
ij_java_spaces_within_method_call_parentheses = false
|
||||
ij_java_spaces_within_method_parentheses = false
|
||||
ij_java_spaces_within_parentheses = false
|
||||
ij_java_spaces_within_record_header = false
|
||||
ij_java_spaces_within_switch_parentheses = false
|
||||
ij_java_spaces_within_synchronized_parentheses = false
|
||||
ij_java_spaces_within_try_parentheses = false
|
||||
ij_java_spaces_within_while_parentheses = false
|
||||
ij_java_special_else_if_treatment = true
|
||||
ij_java_static_field_name_prefix =
|
||||
ij_java_static_field_name_suffix =
|
||||
ij_java_subclass_name_prefix =
|
||||
ij_java_subclass_name_suffix = Impl
|
||||
ij_java_ternary_operation_signs_on_next_line = false
|
||||
ij_java_ternary_operation_wrap = off
|
||||
ij_java_test_name_prefix =
|
||||
ij_java_test_name_suffix = Test
|
||||
ij_java_throws_keyword_wrap = off
|
||||
ij_java_throws_list_wrap = off
|
||||
ij_java_use_external_annotations = false
|
||||
ij_java_use_fq_class_names = false
|
||||
ij_java_use_relative_indents = false
|
||||
ij_java_use_single_class_imports = true
|
||||
ij_java_variable_annotation_wrap = off
|
||||
ij_java_visibility = public
|
||||
ij_java_while_brace_force = never
|
||||
ij_java_while_on_new_line = false
|
||||
ij_java_wrap_comments = false
|
||||
ij_java_wrap_first_method_in_call_chain = true
|
||||
ij_java_wrap_long_lines = false
|
||||
indent_style = space
|
||||
indent_size = tab
|
||||
tab_width = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
name: Auto Deploy Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master","2.0","2.1","2.2" ]
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Cache Maven Repository
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
|
||||
key: jetlinks-community-maven-repository
|
||||
- name: Build with Maven
|
||||
run: ./mvnw clean install -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")" -Dmaven.test.skip=true -Pbuild && cd jetlinks-standalone && docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) .
|
||||
run: mvn -B package -Pbuild && cd jetlinks-standalone && mvn docker:build
|
||||
- name: Login Docker Repo
|
||||
run: echo "${{ secrets.ALIYUN_DOCKER_REPO_PWD }}" | docker login registry.cn-shenzhen.aliyuncs.com -u ${{ secrets.ALIYUN_DOCKER_REPO_USERNAME }} --password-stdin
|
||||
- name: Push Docker
|
||||
run: docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
run: docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
name: Auto Deploy Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "2.10","2.11" ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Cache Maven Repository
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
|
||||
- name: Build with Maven
|
||||
run: ./mvnw clean install -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")" -Dmaven.test.skip=true -Pbuild
|
||||
- name: Get Maven project version
|
||||
id: maven-version
|
||||
run: |
|
||||
VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Project version: $VERSION"
|
||||
- name: Log in to Aliyun ACR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.cn-shenzhen.aliyuncs.com
|
||||
username: ${{ secrets.ALIYUN_DOCKER_REPO_USERNAME }}
|
||||
password: ${{ secrets.ALIYUN_DOCKER_REPO_PWD }}
|
||||
- name: build and docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./jetlinks-standalone
|
||||
file: ./jetlinks-standalone/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
push: true
|
||||
tags: |
|
||||
registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:${{ steps.maven-version.outputs.version }}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Cache Maven Repository
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: jetlinks-community-maven-repository
|
||||
- name: Build with Maven
|
||||
run: ./mvnw package -Dmaven.test.skip=true -Pbuild
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Pull Request 2.10 with java17
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "2.10","2.11" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
- name: Cache Maven Repository
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: jetlinks-community-maven-repository
|
||||
- name: Build with Maven
|
||||
run: ./mvnw package -Dmaven.test.skip=true -Pbuild
|
||||
|
|
@ -20,14 +20,10 @@ hs_err_pid*
|
|||
**/transaction-logs/
|
||||
!/.mvn/wrapper/maven-wrapper.jar
|
||||
*.db
|
||||
/data/
|
||||
data/
|
||||
/static/
|
||||
/upload
|
||||
/ui/upload/
|
||||
docker/data
|
||||
!device-simulator.jar
|
||||
!demo-protocol-1.0.jar
|
||||
application-local.yml
|
||||
dev/
|
||||
.DS_Store
|
||||
.java-version
|
||||
!demo-protocol-1.0.jar
|
||||
|
|
@ -1 +1 @@
|
|||
distributionUrl=https://archive.apache.org/dist/maven/maven-3/3.9.3/binaries/apache-maven-3.9.3-bin.zip
|
||||
distributionUrl=http://mirrors.hust.edu.cn/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="JetLinksApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<option name="ACTIVE_PROFILES" value="default,dev,local" />
|
||||
<module name="jetlinks-standalone" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="org.jetlinks.community.standalone.JetLinksApplication" />
|
||||
<option name="VM_PARAMETERS" value="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.scripting/javax.script=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED -XX:+EnableDynamicAgentLoading" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
140
README.md
140
README.md
|
|
@ -1,66 +1,47 @@
|
|||
# JetLinks 物联网基础平台
|
||||
|
||||

|
||||

|
||||
[](https://app.codacy.com/gh/jetlinks/jetlinks-community?utm_source=github.com&utm_medium=referral&utm_content=jetlinks/jetlinks-community&utm_campaign=Badge_Grade_Settings)
|
||||
[](https://www.oscs1024.com/project/jetlinks/jetlinks-community?ref=badge_small)
|
||||
[](https://github.com/jetlinks/jetlinks-community)
|
||||
[](https://gitee.com/jetlinks/jetlinks-community/stargazers)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
[](https://qm.qq.com/q/kLT3trlXuE)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=jiirLiyFUecy_gsankzVQ-cl6SrZCnv9&&jump_from=webapi)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=Gj47w9kg7TlV5ceD5Bqew_M_O0PIjh_l&jump_from=webapi)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=K5m27CkhDn3B_Owr-g6rfiTBC5DKEY59&jump_from=webapi)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi)
|
||||
|
||||
JetLinks 2.1x 基于Java 17,Spring Boot 3.x,WebFlux,Netty,Vert.x,Reactor等开发,
|
||||
JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发,
|
||||
是一个开箱即用,可二次开发的企业级物联网基础平台。平台实现了物联网相关的众多基础功能,
|
||||
能帮助你快速建立物联网相关业务系统。
|
||||
|
||||
|
||||
## 核心特性
|
||||
|
||||
#### 开放源代码
|
||||
支持统一物模型管理,多种设备,多种厂家,统一管理。
|
||||
|
||||
全部源代码开放,可自由拓展功能,不再受制于人.前后端分离,接口全开放。
|
||||
统一设备连接管理,多协议适配(TCP,MQTT,UDP,CoAP,HTTP等),屏蔽网络编程复杂性,灵活接入不同厂家不同协议的设备。
|
||||
|
||||
#### 部署简单
|
||||
灵活的规则引擎,设备告警,消息通知,数据转发.可基于SQL进行复杂的数据处理逻辑.
|
||||
|
||||
最小化运行仅需要`java 17`,`redis`,`timescaledb`即可,无需部署大量中间件。
|
||||
地理位置:统一管理地理位置信息,支持区域搜索.
|
||||
|
||||
#### 统一设备接入,海量设备管理
|
||||
数据可视化: 实现拖拽配置数据图表,设备组态等.
|
||||
|
||||
TCP/UDP/MQTT/HTTP、TLS/DTLS、不同厂商、不同设备、不同报文、统一接入,统一管理。
|
||||
|
||||
#### 规则引擎
|
||||
|
||||
灵活的规则模型配置,支持多种规则模型以及自定义规则模型. 设备告警,场景联动,均由统一的规则引擎管理。
|
||||
|
||||
#### 数据权限控制
|
||||
|
||||
灵活的非侵入数据权限控制。可实现菜单、按钮、数据三维维度的数据权限控制。可控制单条数据的操作权限。
|
||||
官方QQ群: `2021514`
|
||||
|
||||
## 技术栈
|
||||
|
||||
1. [Spring Boot 3.4.x](https://spring.io/projects/spring-boot)
|
||||
1. [Spring Boot 2.2.x](https://spring.io/projects/spring-boot)
|
||||
2. [Spring WebFlux](https://spring.io/) 响应式Web支持
|
||||
3. [R2DBC](https://r2dbc.io/) 响应式关系型数据库驱动
|
||||
4. [Project Reactor](https://projectreactor.io/) 响应式编程框架
|
||||
5. [Netty](https://netty.io/),[Vert.x](https://vertx.io/) 高性能网络编程框架
|
||||
6. [hsweb framework 4](https://github.com/hs-web) 业务功能基础框架
|
||||
7. [ElasticSearch](https://www.elastic.co/cn/products/enterprise-search) 全文检索,日志,时序数据存储 (可选)
|
||||
8. [TDengine](https://www.taosdata.com/) 设备时序数据存储(可选)
|
||||
9. [Redis](https://redis.io/) 缓存数据
|
||||
10. [TimescaleDB](https://www.timescale.com/) 时序数据存储(可选)
|
||||
11. [PostgreSQL](https://www.postgresql.org) 业务功能数据管理
|
||||
4. [Netty](https://netty.io/) ,[Vert.x](https://vertx.io/) 高性能网络编程框架
|
||||
5. [ElasticSearch](https://www.elastic.co/cn/products/enterprise-search) 全文检索,日志,时序数据存储
|
||||
6. [PostgreSQL](https://www.postgresql.org) 业务功能数据管理
|
||||
7. [hsweb framework 4](https://github.com/hs-web) 业务功能基础框架
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||

|
||||
|
||||
## 设备接入流程
|
||||
|
||||

|
||||

|
||||
|
||||
## 模块
|
||||
|
||||
|
|
@ -68,61 +49,48 @@ TCP/UDP/MQTT/HTTP、TLS/DTLS、不同厂商、不同设备、不同报文、统
|
|||
--jetlinks-community
|
||||
------|----docker
|
||||
------|------|----dev-env # 启动开发环境
|
||||
------|------|----run-all # 启动全部,通过http://localhost:8848 访问系统.
|
||||
------|------|----run-all # 启动全部,通过http://localhost:9000 访问系统.
|
||||
------|----jetlinks-components # 公共组件模块
|
||||
------|-------|----common-component # 通用组件.
|
||||
------|-------|----configuration-component # 通用配置.
|
||||
------|-------|----dashboard-component # 仪表盘.
|
||||
------|-------|----datasource-component # 数据源.
|
||||
------|-------|----elasticsearch-component # elasticsearch集成.
|
||||
------|-------|----gateway-component # 网关组件,消息网关,设备接入.
|
||||
------|-------|----io-component # IO 组件,Excel导入导出等.
|
||||
------|-------|----logging-component # 日志组件
|
||||
------|-------|----network-component # 网络组件,MQTT,TCP,CoAP,UDP等
|
||||
------|-------|----notify-component # 通知组件,短信,右键等通知
|
||||
------|-------|----protocol-component # 协议组件
|
||||
------|-------|----relation-component # 关系组件
|
||||
------|-------|----rule-engine-component # 规则引擎
|
||||
------|-------|----script-component # 脚本组件
|
||||
------|-------|----timeseries-component # 时序数据组件
|
||||
------|-------|----tdengine-component # TDengine集成
|
||||
------|-------|----things-component # 物组件
|
||||
------|----jetlinks-manager # 业务管理模块
|
||||
------|-------|----authentication-manager # 用户,权限管理
|
||||
------|-------|----device-manager # 设备管理
|
||||
------|-------|----logging-manager # 日志管理
|
||||
------|-------|----network-manager # 网络组件管理
|
||||
------|-------|----notify-manager # 通知管理
|
||||
------|-------|----visualization-manager # 数据可视化管理
|
||||
------|-------|----rule-engine-manager # 规则引擎管理
|
||||
------|----jetlinks-standalone # 服务启动模块
|
||||
------|----simulator # 设备模拟器
|
||||
```
|
||||
|
||||
## 服务支持
|
||||
|
||||
我们提供了各种服务方式帮助您深入了解物联网平台和代码,通过产品文档、技术交流群、付费教学等方式,你将获得如下服务:
|
||||
|
||||
| 服务项 | 服务内容 | 服务收费 | 服务方式 |
|
||||
|----------|-----------------------------------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 基础问题答疑 | 问题答疑 | 免费 | 技术交流群支持 [](https://qm.qq.com/cgi-bin/qm/qr?k=jiirLiyFUecy_gsankzVQ-cl6SrZCnv9&&jump_from=webapi) [](https://qm.qq.com/cgi-bin/qm/qr?k=Gj47w9kg7TlV5ceD5Bqew_M_O0PIjh_l&jump_from=webapi) [](https://qm.qq.com/cgi-bin/qm/qr?k=K5m27CkhDn3B_Owr-g6rfiTBC5DKEY59&jump_from=webapi) [](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi) [](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi) |
|
||||
| 系统部署 | 系统部署 | 免费 | 文档自助。[源码部署](https://hanta.yuque.com/px7kg1/yfac2l/vvoa3u2ztymtp4oh) [Docker部署](https://hanta.yuque.com/px7kg1/yfac2l/mzq23z4iey5ev1a5) |
|
||||
| 产品使用 | 教学产品各功能使用 | 免费 | 文档自助。[产品文档](https://hanta.yuque.com/px7kg1/yfac2l) |
|
||||
| 二次开发 | 教学平台源码开发过程、工具使用等; | 免费 | 文档自助。[开发文档](https://hanta.yuque.com/px7kg1/dev) |
|
||||
| 系统部署 | 在客户指定的网络和硬件环境中完成社区版服务部署;提供**模拟**设备接入到平台中,并能完成正常设备上线、数据上下行 | 199元 | 线上部署支持 |
|
||||
| 技术支持 | 提供各类部署、功能使用中遇到的问题答疑 | 100元 | 半小时内 线上远程支持 |
|
||||
| 设备接入协议开发 | 根据提供的设备型号,编写并提供接入平台协议包的源码。 | 3000+元 | 定制化开发 |
|
||||
| 其他服务 | 企业版源码购买;定制化开发;定制化时长、功能服务等 | 面议 | 面议 |
|
||||
|
||||
### **付费**服务支持或商务合作请联系
|
||||
|
||||

|
||||
|
||||
## 文档
|
||||
|
||||
[产品文档](https://hanta.yuque.com/px7kg1/yfac2l)
|
||||
[快速开始](https://hanta.yuque.com/px7kg1/yfac2l/raspyc4p1asfuxks)
|
||||
[开发文档](https://hanta.yuque.com/px7kg1/nn1gdr)
|
||||
[快速开始](http://doc.jetlinks.cn/basics-guide/quick-start.html)
|
||||
[开发文档](http://doc.jetlinks.cn/dev-guide/start.html)
|
||||
[常见问题](http://doc.jetlinks.cn/common-problems/network-components.html)
|
||||
|
||||
[](https://starchart.cc/jetlinks/jetlinks-community)
|
||||
## 许可版本
|
||||
|
||||
| 功能 | 社区版 | 专业版 | 企业版 |
|
||||
| ---- | ---- | ---- | ----- |
|
||||
| 开放源代码 | ✅ | ✅ | ✅ |
|
||||
| 设备管理,设备接入| ✅ | ✅ | ✅ |
|
||||
| 多消息协议支持| ✅ | ✅ | ✅ |
|
||||
| 规则引擎-设备告警 | ✅ | ✅ | ✅ |
|
||||
| 规则引擎-数据转发 | ✅ | ✅ | ✅ |
|
||||
| 系统监控,数据统计 | ✅ | ✅ | ✅ |
|
||||
| 邮件消息通知 | ✅ | ✅ | ✅ |
|
||||
| 微信企业消息 | ✅ | ✅ | ✅ |
|
||||
| 钉钉消息通知 | ✅ | ✅ | ✅ |
|
||||
| MQTT(TLS) | ✅ | ✅ | ✅ |
|
||||
| TCP(TLS) | ✅ | ✅ | ✅ |
|
||||
| CoAP(DTLS) | ⭕ | ✅ | ✅ |
|
||||
| Http,WebSocket(TLS) | ⭕ | ✅ | ✅ |
|
||||
| 数据转发:MQTT,HTTP,Kafka... | ⭕ | ✅ | ✅ |
|
||||
| Geo地理位置支持 | ⭕ | ✅ | ✅ |
|
||||
| OpenAPI | ⭕ | ✅ | ✅ |
|
||||
| 多租户(建设中) | ⭕ | ✅ | ✅ |
|
||||
| 集群支持 | ⭕ | ✅ | ✅ |
|
||||
| QQ群技术支持 | ⭕ | ✅ | ✅ |
|
||||
| 一对一技术支持 | ⭕ | ⭕ | ✅ |
|
||||
| 微服务架构(建设中) | ⭕ | ⭕ | ✅ |
|
||||
| 统一认证(建设中) | ⭕ | ⭕ | ✅ |
|
||||
| 选配业务模块(建设中) | ⭕ | ⭕ | ✅ |
|
||||
| 定制开发 | ⭕ | ⭕ | ✅ |
|
||||
| 商业限制 | 无 | 单个项目 | 无 |
|
||||
| 定价 | 免费 | 联系我们 | 联系我们 |
|
||||
|
||||
⚠️:所有版本均不可发布为与JetLinks同类的产品进行二次销售.
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dockerImage=registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
./mvnw clean package -Dmaven.test.skip=true -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||
./mvnw clean package -Dmaven.test.skip=true
|
||||
if [ $? -ne 0 ];then
|
||||
echo "构建失败!"
|
||||
else
|
||||
cd ./jetlinks-standalone || exit
|
||||
docker build -t "$dockerImage" . && docker push "$dockerImage"
|
||||
../mvnw docker:build && docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone
|
||||
fi
|
||||
BIN
device-flow.png
BIN
device-flow.png
Binary file not shown.
|
Before Width: | Height: | Size: 376 KiB |
|
|
@ -1,24 +1,50 @@
|
|||
version: '2'
|
||||
services:
|
||||
redis:
|
||||
image: redis:6
|
||||
container_name: jetlinks-ce-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- "./data/redis:/data"
|
||||
command: redis-server --appendonly yes
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
postgres:
|
||||
image: timescale/timescaledb:latest-pg16
|
||||
container_name: jetlinks-postgres-16
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- "./data/postgres:/var/lib/postgresql/data"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: jetlinks
|
||||
POSTGRES_DB: jetlinks
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
TZ: Asia/Shanghai
|
||||
redis:
|
||||
image: redis:5.0.4
|
||||
container_name: jetlinks-ce-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- "redis-volume:/data"
|
||||
command: redis-server --appendonly yes
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
elasticsearch:
|
||||
image: elasticsearch:6.8.6
|
||||
container_name: jetlinks-ce-elasticsearch
|
||||
environment:
|
||||
ES_JAVA_OPTS: -Djava.net.preferIPv4Stack=true -Xms1g -Xmx1g
|
||||
transport.host: 0.0.0.0
|
||||
discovery.type: single-node
|
||||
bootstrap.memory_lock: "true"
|
||||
discovery.zen.minimum_master_nodes: 1
|
||||
discovery.zen.ping.unicast.hosts: elasticsearch
|
||||
ports:
|
||||
- "9200:9200"
|
||||
- "9300:9300"
|
||||
kibana:
|
||||
image: kibana:6.8.6
|
||||
container_name: jetlinks-ce-kibana
|
||||
environment:
|
||||
ELASTICSEARCH_URL: http://elasticsearch:9200
|
||||
links:
|
||||
- elasticsearch:elasticsearch
|
||||
ports:
|
||||
- "5601:5601"
|
||||
depends_on:
|
||||
- elasticsearch
|
||||
postgres:
|
||||
image: postgres:11-alpine
|
||||
container_name: jetlinks-ce-postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- "postgres-volume:/var/lib/postgresql/data"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: jetlinks
|
||||
POSTGRES_DB: jetlinks
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
postgres-volume:
|
||||
redis-volume:
|
||||
|
|
@ -1 +0,0 @@
|
|||
data
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
version: '2'
|
||||
services:
|
||||
redis:
|
||||
image: redis:5.0.4
|
||||
container_name: jetlinks-ce-redis
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
volumes:
|
||||
- "redis-volume:/data"
|
||||
command: redis-server --appendonly yes
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
ui:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.3.0
|
||||
container_name: jetlinks-ce-ui
|
||||
ports:
|
||||
- 9000:80
|
||||
environment:
|
||||
- "API_BASE_PATH=http://jetlinks:8848/" #API根路径
|
||||
volumes:
|
||||
- "jetlinks-upload-volume:/usr/share/nginx/html/upload"
|
||||
links:
|
||||
- jetlinks:jetlinks
|
||||
jetlinks:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.3.0-SNAPSHOT
|
||||
container_name: jetlinks-ce
|
||||
ports:
|
||||
- 8848:8848 # API端口
|
||||
- 1883:1883 # MQTT端口
|
||||
- 8000:8000 # 预留
|
||||
- 8001:8001 # 预留
|
||||
- 8002:8002 # 预留
|
||||
- 9000:9000 # elasticsearch
|
||||
- 6379:6379 # redis
|
||||
volumes:
|
||||
- "jetlinks-upload-volume:/static/upload" # 持久化上传的文件
|
||||
- "jetlinks-data-volume:/data"
|
||||
environment:
|
||||
# - "JAVA_OPTS=-Xms4g -Xmx18g -XX:+UseG1GC"
|
||||
- "spring.profiles.active=dev,embedded" #使用dev和embedded环境.
|
||||
- "hsweb.file.upload.static-location=http://127.0.0.1:8848/upload" #上传的静态文件访问根地址,为ui的地址.
|
||||
- "logging.level.io.r2dbc=warn"
|
||||
- "spring.redis.host=redis"
|
||||
- "logging.level.org.springframework.data=warn"
|
||||
- "logging.level.org.springframework=warn"
|
||||
- "logging.level.org.jetlinks=warn"
|
||||
- "logging.level.org.hswebframework=warn"
|
||||
- "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
|
||||
volumes:
|
||||
jetlinks-upload-volume:
|
||||
jetlinks-data-volume:
|
||||
|
|
@ -1,92 +1,100 @@
|
|||
version: '3'
|
||||
version: '2'
|
||||
services:
|
||||
redis:
|
||||
image: redis:6
|
||||
container_name: jetlinks-ce-redis
|
||||
# ports:
|
||||
# - "6379:6379" # 仅供jetlinks-ce访问
|
||||
volumes:
|
||||
- "./data/redis:/data"
|
||||
command: redis-server --appendonly yes --requirepass "JetLinks@redis"
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
healthcheck:
|
||||
test: [ "CMD", "redis-cli", "-h", "localhost", "-p", "6379", "-a", "JetLinks@redis", "ping" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
postgres:
|
||||
image: timescale/timescaledb:latest-pg16
|
||||
container_name: jetlinks-ce-postgres
|
||||
# ports:
|
||||
# - "5432:5432" # 仅供jetlinks-ce访问
|
||||
volumes:
|
||||
- "./data/postgres:/var/lib/postgresql/data"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: JetLinks@postgres
|
||||
POSTGRES_DB: jetlinks
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
TZ: Asia/Shanghai
|
||||
healthcheck:
|
||||
test: [ "CMD", "pg_isready", "-U", "postgres" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
jetlinks:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:2.11.0-SNAPSHOT
|
||||
container_name: jetlinks-ce
|
||||
ports:
|
||||
- "8848:8848" # 平台访问端口
|
||||
- "1883-1890:1883-1890" # 预留
|
||||
- "8800-8810:8800-8810" # 预留
|
||||
- "5060-5061:5060-5061" # 预留
|
||||
volumes:
|
||||
- "./data/jetlinks:/application/data"
|
||||
environment:
|
||||
- "JAVA_OPTS=-Duser.language=zh"
|
||||
- "TZ=Asia/Shanghai"
|
||||
- "EXTERNAL_HOST=local-host.cn" # 对外提供访问的域名或者ip地址
|
||||
- "EXTERNAL_PORT=8848" # 对外提供访问的端口,修改了端口映射同时也需要修改这里.
|
||||
- "ADMIN_USER_PASSWORD=JetLinks.C0mmVn1ty" # admin用户的初始密码
|
||||
- "spring.r2dbc.url=r2dbc:postgresql://postgres:5432/jetlinks" #数据库连接地址
|
||||
- "spring.r2dbc.username=postgres"
|
||||
- "spring.r2dbc.password=JetLinks@postgres"
|
||||
- "spring.data.redis.host=redis"
|
||||
- "spring.data.redis.port=6379"
|
||||
- "file.manager.storage-base-path=/application/data/files"
|
||||
- "spring.data.redis.password=JetLinks@redis"
|
||||
- "logging.level.io.r2dbc=warn"
|
||||
- "logging.level.org.springframework.data=warn"
|
||||
- "logging.level.org.springframework=warn"
|
||||
- "logging.level.org.jetlinks=warn"
|
||||
- "logging.level.org.hswebframework=warn"
|
||||
- "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
|
||||
- "network.resources[0]=0.0.0.0:8800-8810/tcp"
|
||||
- "network.resources[1]=0.0.0.0:1883-1890"
|
||||
- "hsweb.cors.enable=true"
|
||||
- "hsweb.cors.configs[0].path=/**"
|
||||
- "hsweb.cors.configs[0].allowed-credentials=true"
|
||||
- "hsweb.cors.configs[0].allowed-headers=*"
|
||||
- "hsweb.cors.configs[0].allowed-origins=*"
|
||||
- "hsweb.cors.configs[0].allowed-methods[0]=GET"
|
||||
- "hsweb.cors.configs[0].allowed-methods[1]=POST"
|
||||
- "hsweb.cors.configs[0].allowed-methods[2]=PUT"
|
||||
- "hsweb.cors.configs[0].allowed-methods[3]=PATCH"
|
||||
- "hsweb.cors.configs[0].allowed-methods[4]=DELETE"
|
||||
- "hsweb.cors.configs[0].allowed-methods[5]=OPTIONS"
|
||||
links:
|
||||
- redis:redis
|
||||
- postgres:postgres
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
# ui: # 如果通过访问内嵌的前端存在问题,可以使用这个镜像
|
||||
# image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.11.0
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# environment:
|
||||
# API_BASE_PATH: "http://jetlinks:8848/"
|
||||
# TZ: Asia/Shanghai
|
||||
|
||||
redis:
|
||||
image: redis:5.0.4
|
||||
container_name: jetlinks-ce-redis
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
volumes:
|
||||
- "redis-volume:/data"
|
||||
command: redis-server --appendonly yes
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
elasticsearch:
|
||||
image: elasticsearch:6.8.6
|
||||
container_name: jetlinks-ce-elasticsearch
|
||||
environment:
|
||||
ES_JAVA_OPTS: -Djava.net.preferIPv4Stack=true -Xms1g -Xmx1g
|
||||
transport.host: 0.0.0.0
|
||||
discovery.type: single-node
|
||||
bootstrap.memory_lock: "true"
|
||||
discovery.zen.minimum_master_nodes: 1
|
||||
discovery.zen.ping.unicast.hosts: elasticsearch
|
||||
volumes:
|
||||
- elasticsearch-volume:/usr/share/elasticsearch/data
|
||||
# ports:
|
||||
# - "9200:9200"
|
||||
# - "9300:9300"
|
||||
kibana:
|
||||
image: kibana:6.8.6
|
||||
container_name: jetlinks-ce-kibana
|
||||
environment:
|
||||
ELASTICSEARCH_URL: http://elasticsearch:9200
|
||||
links:
|
||||
- elasticsearch:elasticsearch
|
||||
ports:
|
||||
- "5602:5601"
|
||||
depends_on:
|
||||
- elasticsearch
|
||||
postgres:
|
||||
image: postgres:11-alpine
|
||||
container_name: jetlinks-ce-postgres
|
||||
volumes:
|
||||
- "postgres-volume:/var/lib/postgresql/data"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: jetlinks
|
||||
POSTGRES_DB: jetlinks
|
||||
TZ: Asia/Shanghai
|
||||
ui:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.3.0
|
||||
container_name: jetlinks-ce-ui
|
||||
ports:
|
||||
- 9000:80
|
||||
environment:
|
||||
- "API_BASE_PATH=http://jetlinks:8848/" #API根路径
|
||||
volumes:
|
||||
- "jetlinks-volume:/usr/share/nginx/html/upload"
|
||||
links:
|
||||
- jetlinks:jetlinks
|
||||
jetlinks:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.3.0-SNAPSHOT
|
||||
container_name: jetlinks-ce
|
||||
ports:
|
||||
- 8848:8848 # API端口
|
||||
- 1883:1883 # MQTT端口
|
||||
- 8000:8000 # 预留
|
||||
- 8001:8001 # 预留
|
||||
- 8002:8002 # 预留
|
||||
volumes:
|
||||
- "jetlinks-volume:/static/upload" # 持久化上传的文件
|
||||
environment:
|
||||
# - "JAVA_OPTS=-Xms4g -Xmx18g -XX:+UseG1GC"
|
||||
- "hsweb.file.upload.static-location=http://127.0.0.1:8848/upload" #上传的静态文件访问根地址,为ui的地址.
|
||||
- "spring.r2dbc.url=r2dbc:postgresql://postgres:5432/jetlinks" #数据库连接地址
|
||||
- "spring.r2dbc.username=postgres"
|
||||
- "spring.r2dbc.password=jetlinks"
|
||||
- "elasticsearch.client.host=elasticsearch"
|
||||
- "elasticsearch.client.post=9200"
|
||||
- "spring.redis.host=redis"
|
||||
- "spring.redis.port=6379"
|
||||
- "logging.level.io.r2dbc=warn"
|
||||
- "logging.level.org.springframework.data=warn"
|
||||
- "logging.level.org.springframework=warn"
|
||||
- "logging.level.org.jetlinks=warn"
|
||||
- "logging.level.org.hswebframework=warn"
|
||||
- "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
|
||||
links:
|
||||
- redis:redis
|
||||
- postgres:postgres
|
||||
- elasticsearch:elasticsearch
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
- elasticsearch
|
||||
volumes:
|
||||
postgres-volume:
|
||||
redis-volume:
|
||||
elasticsearch-volume:
|
||||
jetlinks-volume:
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
FROM nginx
|
||||
ADD nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
|
||||
CMD ["sh","docker-entrypoint.sh"]
|
||||
#ADD oauth2 /usr/share/nginx/html/oauth2
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
FROM nginx
|
||||
ADD nginx.conf /etc/nginx/conf.d/default.conf
|
||||
ADD docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
CMD ["sh","/docker-entrypoint.sh"]
|
||||
#ADD oauth2 /usr/share/nginx/html/oauth2
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
API_BASE_PATH=$API_BASE_PATH;
|
||||
|
||||
if [ -z "$API_BASE_PATH" ]; then
|
||||
API_BASE_PATH="http://jetlinks:8844/";
|
||||
fi
|
||||
|
||||
apiUrl="proxy_pass $API_BASE_PATH;"
|
||||
|
||||
sed -i '18c '"$apiUrl"'' /etc/nginx/conf.d/default.conf
|
||||
|
||||
nginx -g "daemon off;"
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
server {
|
||||
listen 80;
|
||||
# gzip config
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_comp_level 9;
|
||||
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
include /etc/nginx/mime.types;
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ^~/api/ {
|
||||
proxy_pass http://jetlinks:8844/;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
<svg id="SvgjsSvg1492" width="792" height="653.5" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs">
|
||||
<defs id="SvgjsDefs1493">
|
||||
<marker id="SvgjsMarker1502" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1503" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1510" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1511" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1518" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1519" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1526" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1527" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1536" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1537" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1546" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1547" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1554" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1555" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1568" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1569" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1576" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1577" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1606" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1607" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1614" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1615" d="M0,2 L14,6 L0,11 L0,2" fill="#000000" stroke="#000000" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
<marker id="SvgjsMarker1700" markerWidth="16" markerHeight="12" refX="16" refY="6" viewBox="0 0 16 12" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path id="SvgjsPath1701" d="M0,2 L14,6 L0,11 L0,2" fill="#323232" stroke="#323232" stroke-width="1">
|
||||
</path>
|
||||
</marker>
|
||||
</defs>
|
||||
<g id="SvgjsG1494" transform="translate(240,33.75)">
|
||||
<path id="SvgjsPath1495" d="M 15 0L 148 0C 168 0 168 45 148 45L 15 45C -5 45 -5 0 15 0Z" stroke="#335f94" stroke-width="3" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1496">
|
||||
<text id="SvgjsText1497" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="103" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="12.55" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1498" dy="16" x="81.5">
|
||||
<tspan id="SvgjsTspan1499" style="text-decoration:;">
|
||||
设备是否有IP能力
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1500">
|
||||
<path id="SvgjsPath1501" d="M240 56.25L160 56.25L160 106.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1502)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1504" width="13" height="16" x="168.5" y="48.25" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1505" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#0000ff" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="46.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1506" dy="16" x="175">
|
||||
<tspan id="SvgjsTspan1507" style="text-decoration:;">
|
||||
是
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1508">
|
||||
<path id="SvgjsPath1509" d="M403 56.25L457.875 56.25L457.875 56.25L512.75 56.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1510)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1512" width="13" height="16" x="451.375" y="48.25" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1513" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#ff0000" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="46.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1514" dy="16" x="457.875">
|
||||
<tspan id="SvgjsTspan1515" style="text-decoration:;">
|
||||
否
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1516">
|
||||
<path id="SvgjsPath1517" d="M148 306.25L209 306.25L209 306.25L270 306.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1518)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1520" width="13" height="16" x="202.5" y="298.25" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1521" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#ff0000" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="296.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1522" dy="16" x="209">
|
||||
<tspan id="SvgjsTspan1523" style="text-decoration:;">
|
||||
否
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1524">
|
||||
<path id="SvgjsPath1525" d="M331 321.25L331 343L331 343L331 364.75" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1526)">
|
||||
</path>
|
||||
</g>
|
||||
<g id="SvgjsG1528" transform="translate(270,364.75)">
|
||||
<path id="SvgjsPath1529" d="M 0 4Q 0 0 4 0L 118 0Q 122 0 122 4L 122 26Q 122 30 118 30L 4 30Q 0 30 0 26Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1530">
|
||||
<text id="SvgjsText1531" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="65" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="5.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1532" dy="16" x="61">
|
||||
<tspan id="SvgjsTspan1533" style="text-decoration:;">
|
||||
上传到平台
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1534">
|
||||
<path id="SvgjsPath1535" d="M331 394.75L331 418L331 418L331 441.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1536)">
|
||||
</path>
|
||||
</g>
|
||||
<g id="SvgjsG1538" transform="translate(533,285.75)">
|
||||
<path id="SvgjsPath1539" d="M 0 20.5L 54.5 0L 109 20.5L 54.5 41Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1540">
|
||||
<text id="SvgjsText1541" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="52" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="10.755" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1542" dy="16" x="54.5">
|
||||
<tspan id="SvgjsTspan1543" style="text-decoration:;">
|
||||
接入方式
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1544">
|
||||
<path id="SvgjsPath1545" d="M533 306.25L462.5 306.25L462.5 306.25L392 306.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1546)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1548" width="91" height="16" x="417" y="298.25" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1549" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="91" fill="#323232" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="296.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1550" dy="16" x="462.5">
|
||||
<tspan id="SvgjsTspan1551" style="text-decoration:;">
|
||||
第三方应用推送
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1552">
|
||||
<path id="SvgjsPath1553" d="M587.5 326.75L587.5 384.125L587.5 384.125L587.5 441.5" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1554)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1556" width="104" height="16" x="535.5" y="376.125" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1557" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="104" fill="#323232" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="374.175" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1558" dy="16" x="587.5">
|
||||
<tspan id="SvgjsTspan1559" style="text-decoration:;">
|
||||
第三方云平台接入
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1560" transform="translate(512.75,25)">
|
||||
<path id="SvgjsPath1561" d="M 0 31.25L 70.5 0L 141 31.25L 70.5 62.5Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1562">
|
||||
<text id="SvgjsText1563" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="91" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="21.6125" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1564" dy="16" x="70.5">
|
||||
<tspan id="SvgjsTspan1565" style="text-decoration:;">
|
||||
是否已接入网关
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1566">
|
||||
<path id="SvgjsPath1567" d="M583.25 87.5L583.25 146.25L220 146.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1568)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1570" width="13" height="16" x="424.5" y="138.25" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1571" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#3333ff" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="136.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1572" dy="16" x="431">
|
||||
<tspan id="SvgjsTspan1573" style="text-decoration:;">
|
||||
是
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1574">
|
||||
<path id="SvgjsPath1575" d="M653.75 56.25L708 56.25L708 148.75" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1576)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1578" width="13" height="16" x="701.5" y="67.375" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1579" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#ff0000" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="65.425" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1580" dy="16" x="708">
|
||||
<tspan id="SvgjsTspan1581" style="text-decoration:;">
|
||||
否
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1582" transform="translate(649,148.75)">
|
||||
<path id="SvgjsPath1583" d="M 0 4Q 0 0 4 0L 114 0Q 118 0 118 4L 118 39Q 118 43 114 43L 4 43Q 0 43 0 39Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1584">
|
||||
<text id="SvgjsText1585" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="52" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="11.55" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1586" dy="16" x="59">
|
||||
<tspan id="SvgjsTspan1587" style="text-decoration:;">
|
||||
联系我们
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1588" transform="translate(25,266.25)">
|
||||
<path id="SvgjsPath1589" d="M 0 40L 61.5 0L 123 40L 61.5 80Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1590">
|
||||
<text id="SvgjsText1591" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="91" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="14.45" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1592" dy="16" x="61.5">
|
||||
<tspan id="SvgjsTspan1593" style="text-decoration:;">
|
||||
是否使用
|
||||
</tspan>
|
||||
</tspan>
|
||||
<tspan id="SvgjsTspan1594" dy="16" x="61.5">
|
||||
<tspan id="SvgjsTspan1595" style="text-decoration:;">
|
||||
JetLinks官方协
|
||||
</tspan>
|
||||
</tspan>
|
||||
<tspan id="SvgjsTspan1596" dy="16" x="61.5">
|
||||
<tspan id="SvgjsTspan1597" style="text-decoration:;">
|
||||
议
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1598" transform="translate(100,106.25)">
|
||||
<path id="SvgjsPath1599" d="M 0 40L 60 0L 120 40L 60 80Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1600">
|
||||
<text id="SvgjsText1601" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="78" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="30.45" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1602" dy="16" x="60">
|
||||
<tspan id="SvgjsTspan1603" style="text-decoration:;">
|
||||
是否直连平台
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1604">
|
||||
<path id="SvgjsPath1605" d="M100 146.25L86.5 146.25L86.5 266.25" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1606)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1608" width="13" height="16" x="80" y="191.5" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1609" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#3333ff" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="189.55" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1610" dy="16" x="86.5">
|
||||
<tspan id="SvgjsTspan1611" style="text-decoration:;">
|
||||
是
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1612">
|
||||
<path id="SvgjsPath1613" d="M160 186.25L160 236L587.5 236L587.5 285.75" stroke="#000000" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1614)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1616" width="13" height="16" x="367.25" y="228" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1617" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#ff0000" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="226.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1618" dy="16" x="373.75">
|
||||
<tspan id="SvgjsTspan1619" style="text-decoration:;">
|
||||
否
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1620" transform="translate(489,441.5)">
|
||||
<path id="SvgjsPath1621" d="M 0 0L 197 0L 197 187L 0 187Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1622">
|
||||
<text id="SvgjsText1623" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="0" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="83.55" transform="rotate(0)">
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1624" transform="translate(512.75,449.875)">
|
||||
<path id="SvgjsPath1625" d="M 0 4Q 0 0 4 0L 149 0Q 153 0 153 4L 153 20.25Q 153 24.25 149 24.25L 4 24.25Q 0 24.25 0 20.25Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1626">
|
||||
<text id="SvgjsText1627" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="46" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="2.175" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1628" dy="16" x="76.5">
|
||||
<tspan id="SvgjsTspan1629" style="text-decoration:;">
|
||||
NB-IoT
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1630" transform="translate(512.75,519.875)">
|
||||
<path id="SvgjsPath1631" d="M 0 4Q 0 0 4 0L 149 0Q 153 0 153 4L 153 22.875Q 153 26.875 149 26.875L 4 26.875Q 0 26.875 0 22.875Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1632">
|
||||
<text id="SvgjsText1633" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="65" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="3.4875" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1634" dy="16" x="76.5">
|
||||
<tspan id="SvgjsTspan1635" style="text-decoration:;">
|
||||
阿里云平台
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1636" transform="translate(512.75,556.125)">
|
||||
<path id="SvgjsPath1637" d="M 0 4Q 0 0 4 0L 149 0Q 153 0 153 4L 153 24Q 153 28 149 28L 4 28Q 0 28 0 24Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1638">
|
||||
<text id="SvgjsText1639" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="78" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="4.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1640" dy="16" x="76.5">
|
||||
<tspan id="SvgjsTspan1641" style="text-decoration:;">
|
||||
海康视频平台
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1642" transform="translate(512.75,592.5)">
|
||||
<path id="SvgjsPath1643" d="M 0 4Q 0 0 4 0L 149 0Q 153 0 153 4L 153 20.25Q 153 24.25 149 24.25L 4 24.25Q 0 24.25 0 20.25Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1644">
|
||||
<text id="SvgjsText1645" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="39" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="2.175" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1646" dy="16" x="76.5">
|
||||
<tspan id="SvgjsTspan1647" style="text-decoration:;">
|
||||
自定义
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1648" transform="translate(235,441.25)">
|
||||
<path id="SvgjsPath1649" d="M 0 0L 192 0L 192 187.5L 0 187.5Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1650">
|
||||
<text id="SvgjsText1651" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="0" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="83.8" transform="rotate(0)">
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1652" transform="translate(247.5,469.75)">
|
||||
<path id="SvgjsPath1653" d="M 0 4Q 0 0 4 0L 154.5 0Q 158.5 0 158.5 4L 158.5 26Q 158.5 30 154.5 30L 4 30Q 0 30 0 26Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1654">
|
||||
<text id="SvgjsText1655" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="121" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="5.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1656" dy="16" x="79.5">
|
||||
<tspan id="SvgjsTspan1657" style="text-decoration:;">
|
||||
MQTT Server,Client
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1658" transform="translate(254.25,434.75)">
|
||||
<path id="SvgjsPath1659" d="M 0 0L 160 0L 160 40L 0 40Z" stroke="none" fill="none">
|
||||
</path>
|
||||
<g id="SvgjsG1660">
|
||||
<text id="SvgjsText1661" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="52" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="10.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1662" dy="16" x="80">
|
||||
<tspan id="SvgjsTspan1663" style="text-decoration:;">
|
||||
接入平台
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1664" transform="translate(247.5,507)">
|
||||
<path id="SvgjsPath1665" d="M 0 4Q 0 0 4 0L 154.5 0Q 158.5 0 158.5 4L 158.5 25.75Q 158.5 29.75 154.5 29.75L 4 29.75Q 0 29.75 0 25.75Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1666">
|
||||
<text id="SvgjsText1667" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="109" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="4.925" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1668" dy="16" x="79.5">
|
||||
<tspan id="SvgjsTspan1669" style="text-decoration:;">
|
||||
TCP Server,Client
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1670" transform="translate(247.5,546.75)">
|
||||
<path id="SvgjsPath1671" d="M 0 4Q 0 0 4 0L 69.5 0Q 73.5 0 73.5 4L 73.5 34Q 73.5 38 69.5 38L 4 38Q 0 38 0 34Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1672">
|
||||
<text id="SvgjsText1673" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="40" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="1.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1674" dy="16" x="37">
|
||||
<tspan id="SvgjsTspan1675" style="text-decoration:;">
|
||||
HTTP
|
||||
</tspan>
|
||||
</tspan>
|
||||
<tspan id="SvgjsTspan1676" dy="16" x="37">
|
||||
<tspan id="SvgjsTspan1677" style="text-decoration:;">
|
||||
Server
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1678" transform="translate(335.5,546.75)">
|
||||
<path id="SvgjsPath1679" d="M 0 4Q 0 0 4 0L 66.5 0Q 70.5 0 70.5 4L 70.5 34Q 70.5 38 66.5 38L 4 38Q 0 38 0 34Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1680">
|
||||
<text id="SvgjsText1681" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="40" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="1.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1682" dy="16" x="35.5">
|
||||
<tspan id="SvgjsTspan1683" style="text-decoration:;">
|
||||
CoAP
|
||||
</tspan>
|
||||
</tspan>
|
||||
<tspan id="SvgjsTspan1684" dy="16" x="35.5">
|
||||
<tspan id="SvgjsTspan1685" style="text-decoration:;">
|
||||
Server
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1686" transform="translate(247.5,592.5)">
|
||||
<path id="SvgjsPath1687" d="M 0 4Q 0 0 4 0L 154.5 0Q 158.5 0 158.5 4L 158.5 23Q 158.5 27 154.5 27L 4 27Q 0 27 0 23Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1688">
|
||||
<text id="SvgjsText1689" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="27" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="3.55" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1690" dy="16" x="79.5">
|
||||
<tspan id="SvgjsTspan1691" style="text-decoration:;">
|
||||
UDP
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1692" transform="translate(270,291.25)">
|
||||
<path id="SvgjsPath1693" d="M 0 4Q 0 0 4 0L 118 0Q 122 0 122 4L 122 26Q 122 30 118 30L 4 30Q 0 30 0 26Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#ffffff">
|
||||
</path>
|
||||
<g id="SvgjsG1694">
|
||||
<text id="SvgjsText1695" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="91" fill="#323232" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="5.05" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1696" dy="16" x="61">
|
||||
<tspan id="SvgjsTspan1697" style="text-decoration:;">
|
||||
开发报文解析包
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1698">
|
||||
<path id="SvgjsPath1699" d="M86.5 346.25L86.5 535L235 535" stroke="#323232" stroke-width="1" fill="none" marker-end="url(#SvgjsMarker1700)">
|
||||
</path>
|
||||
<rect id="SvgjsRect1702" width="13" height="16" x="80" y="506.875" fill="#ffffff">
|
||||
</rect>
|
||||
<text id="SvgjsText1703" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="13" fill="#3333ff" font-weight="700" align="top" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="504.925" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1704" dy="16" x="86.5">
|
||||
<tspan id="SvgjsTspan1705" style="text-decoration:;">
|
||||
是
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="SvgjsG1706" transform="translate(512.75,482.875)">
|
||||
<path id="SvgjsPath1707" d="M 0 4Q 0 0 4 0L 70 0Q 74 0 74 4L 74 22.5Q 74 26.5 70 26.5L 4 26.5Q 0 26.5 0 22.5Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1708">
|
||||
<text id="SvgjsText1709" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="26" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="3.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1710" dy="16" x="37">
|
||||
<tspan id="SvgjsTspan1711" style="text-decoration:;">
|
||||
移动
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SvgjsG1712" transform="translate(591.75,482.875)">
|
||||
<path id="SvgjsPath1713" d="M 0 4Q 0 0 4 0L 70 0Q 74 0 74 4L 74 22.5Q 74 26.5 70 26.5L 4 26.5Q 0 26.5 0 22.5Z" stroke="#335f94" stroke-width="2" fill-opacity="1" fill="#3581bb">
|
||||
</path>
|
||||
<g id="SvgjsG1714">
|
||||
<text id="SvgjsText1715" font-family="微软雅黑" text-anchor="middle" font-size="13px" width="26" fill="#ffffff" font-weight="700" align="middle" anchor="middle" family="微软雅黑" size="13px" weight="700" font-style="" y="3.3" transform="rotate(0)">
|
||||
<tspan id="SvgjsTspan1716" dy="16" x="37">
|
||||
<tspan id="SvgjsTspan1717" style="text-decoration:;">
|
||||
电信
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
|
@ -5,8 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>jetlinks-components</artifactId>
|
||||
<groupId>org.jetlinks.community</groupId>
|
||||
<version>2.11.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
<version>1.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
@ -19,11 +18,6 @@
|
|||
<version>${jetlinks.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetlinks</groupId>
|
||||
<artifactId>jetlinks-supports</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-authorization-api</artifactId>
|
||||
|
|
@ -40,69 +34,5 @@
|
|||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetlinks</groupId>
|
||||
<artifactId>reactor-ql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.ruedigermoeller</groupId>
|
||||
<artifactId>fst</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2-mvstore</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetlinks.sdk</groupId>
|
||||
<artifactId>jetlinks-sdk-api</artifactId>
|
||||
<version>${jetlinks.sdk.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-system-dictionary</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.cronutils</groupId>
|
||||
<artifactId>cron-utils</artifactId>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>jakarta.el</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.el</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.expressly</groupId>
|
||||
<artifactId>expressly</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
</project>
|
||||
|
|
@ -1,29 +1,8 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import org.jetlinks.core.config.ConfigKey;
|
||||
import org.jetlinks.core.metadata.MergeOption;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据验证配置常量类
|
||||
*
|
||||
* @author zhouhao
|
||||
* @see ConfigKey
|
||||
*/
|
||||
public interface ConfigMetadataConstants {
|
||||
|
|
@ -36,10 +15,5 @@ public interface ConfigMetadataConstants {
|
|||
ConfigKey<Boolean> allowInput = ConfigKey.of("allowInput", "允许输入", Boolean.TYPE);
|
||||
ConfigKey<Boolean> required = ConfigKey.of("required", "是否必填", Boolean.TYPE);
|
||||
|
||||
ConfigKey<String> format = ConfigKey.of("format", "格式", String.class);
|
||||
|
||||
ConfigKey<String> defaultValue = ConfigKey.of("defaultValue", "默认值", String.class);
|
||||
|
||||
ConfigKey<Boolean> indexEnabled = ConfigKey.of("indexEnabled", "开启索引", Boolean.TYPE);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DynamicOperationType implements OperationType {
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
|
@ -1,225 +1,66 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import lombok.*;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import reactor.core.publisher.Flux;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonDeserialize(using = Interval.IntervalJSONDeserializer.class)
|
||||
@JSONType(deserializer = Interval.IntervalJSONDeserializer.class)
|
||||
public class Interval {
|
||||
|
||||
public static final String year = "y";
|
||||
public static final String quarter = "q";
|
||||
public static final String month = "M";
|
||||
public static final String weeks = "w";
|
||||
public static final String days = "d";
|
||||
public static final String hours = "h";
|
||||
public static final String minutes = "m";
|
||||
public static final String seconds = "s";
|
||||
public static final String millis = "S";
|
||||
public static String year = "y";
|
||||
public static String quarter = "q";
|
||||
public static String month = "M";
|
||||
public static String weeks = "w";
|
||||
public static String days = "d";
|
||||
public static String hours = "h";
|
||||
public static String minutes = "m";
|
||||
public static String seconds = "s";
|
||||
|
||||
private BigDecimal number;
|
||||
|
||||
private String expression;
|
||||
|
||||
public Interval(String expr) {
|
||||
char[] chars = expr.toCharArray();
|
||||
int numIndex = 0;
|
||||
for (char c : expr.toCharArray()) {
|
||||
if (c == '-' || c == '.' || (c >= '0' && c <= '9')) {
|
||||
numIndex++;
|
||||
} else {
|
||||
BigDecimal val = new BigDecimal(chars, 0, numIndex);
|
||||
this.expression = expr.substring(numIndex);
|
||||
this.number = val;
|
||||
}
|
||||
}
|
||||
if (this.expression == null) {
|
||||
throw new IllegalArgumentException("can not parse interval expression:" + expr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (number) + expression;
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval ofSeconds(int seconds) {
|
||||
return of(seconds, Interval.seconds);
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval ofDays(int days) {
|
||||
return of(days, Interval.days);
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval ofWeeks(int weeks) {
|
||||
return of(weeks, Interval.weeks);
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval ofHours(int hours) {
|
||||
return of(hours, Interval.hours);
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval ofMonth(int month) {
|
||||
return of(month, Interval.month);
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval ofMinutes(int month) {
|
||||
return of(month, Interval.minutes);
|
||||
}
|
||||
|
||||
@Generated
|
||||
public static Interval of(int month, String expression) {
|
||||
return new Interval(new BigDecimal(month), expression);
|
||||
}
|
||||
|
||||
public static Interval of(String expr) {
|
||||
return new Interval(expr);
|
||||
}
|
||||
|
||||
public String getDefaultFormat() {
|
||||
switch (getExpression()) {
|
||||
case year:
|
||||
return "yyyy";
|
||||
case quarter:
|
||||
case month:
|
||||
return "yyyy-MM";
|
||||
case days:
|
||||
return "yyyy-MM-dd";
|
||||
case hours:
|
||||
return "MM-dd HH";
|
||||
case minutes:
|
||||
return "MM-dd HH:mm";
|
||||
case seconds:
|
||||
return "HH:mm:ss";
|
||||
default:
|
||||
return "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
}
|
||||
|
||||
public IntervalUnit getUnit() {
|
||||
switch (expression) {
|
||||
case year:
|
||||
return IntervalUnit.YEARS;
|
||||
case quarter:
|
||||
return IntervalUnit.QUARTER;
|
||||
case month:
|
||||
return IntervalUnit.MONTHS;
|
||||
case weeks:
|
||||
return IntervalUnit.WEEKS;
|
||||
case days:
|
||||
return IntervalUnit.DAYS;
|
||||
case hours:
|
||||
return IntervalUnit.HOURS;
|
||||
case minutes:
|
||||
return IntervalUnit.MINUTES;
|
||||
case seconds:
|
||||
return IntervalUnit.SECONDS;
|
||||
case millis:
|
||||
return IntervalUnit.MILLIS;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("unsupported interval express:" + expression);
|
||||
}
|
||||
|
||||
public static class IntervalJSONDeserializer extends JsonDeserializer<Interval> {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Interval deserialize(JsonParser jp, DeserializationContext ctxt) {
|
||||
JsonNode node = jp.getCodec().readTree(jp);
|
||||
|
||||
String currentName = jp.currentName();
|
||||
Object currentValue = jp.getCurrentValue();
|
||||
if (currentName == null || currentValue == null) {
|
||||
return null;
|
||||
char[] number = new char[32];
|
||||
int numIndex = 0;
|
||||
for (char c : expr.toCharArray()) {
|
||||
if (c == '-' || c == '.' || (c >= '0' && c <= '9')) {
|
||||
number[numIndex++] = c;
|
||||
continue;
|
||||
}
|
||||
return of(node.textValue());
|
||||
BigDecimal val = new BigDecimal(number, 0, numIndex);
|
||||
return new Interval(val, expr.substring(numIndex));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class IntervalJSONSerializer extends JsonSerializer<Interval> {
|
||||
|
||||
@Override
|
||||
public void serialize(Interval value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public long toMillis() {
|
||||
return getUnit().toMillis(number.intValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对指定的时间戳按周期取整
|
||||
*
|
||||
* @param timestamp 时间戳
|
||||
* @return 取整后的值
|
||||
*/
|
||||
public long round(long timestamp) {
|
||||
return getUnit().truncatedTo(timestamp, number.intValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按当前周期对指定的时间范围进行迭代,每次迭代一个周期的时间戳
|
||||
*
|
||||
* @param from 时间从
|
||||
* @param to 时间止
|
||||
* @return 迭代器
|
||||
*/
|
||||
public Iterable<Long> iterate(long from, long to) {
|
||||
return getUnit().iterate(from, to, number.intValue());
|
||||
}
|
||||
|
||||
public <T> Flux<T> generate(long from, long to, Function<Long, T> converter) {
|
||||
return Flux
|
||||
.fromIterable(iterate(from, to))
|
||||
.map(converter);
|
||||
}
|
||||
|
||||
public <T> Flux<T> generateWithFormat(long from,
|
||||
long to,
|
||||
String pattern,
|
||||
BiFunction<Long, String, T> converter) {
|
||||
DateTimeFormatter formatter = DateTimeFormat.forPattern(pattern);
|
||||
return generate(from, to, t -> converter.apply(t, new DateTime(t).toString(formatter)));
|
||||
throw new IllegalArgumentException("can not parse interval expression:" + expr);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* 时间间隔单位,可用于计算时间范围的间隔周期
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 1.12
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public enum IntervalUnit {
|
||||
MILLIS(1, ChronoUnit.MILLIS),
|
||||
SECONDS(1, ChronoUnit.SECONDS),
|
||||
MINUTES(1, ChronoUnit.MINUTES),
|
||||
HOURS(1, ChronoUnit.HOURS),
|
||||
DAYS(1, ChronoUnit.DAYS),
|
||||
WEEKS(1, ChronoUnit.WEEKS) {
|
||||
@Override
|
||||
protected LocalDateTime doTruncateTo(LocalDateTime time) {
|
||||
return time.truncatedTo(ChronoUnit.DAYS)
|
||||
.minusDays(time.getDayOfWeek().getValue() - 1);
|
||||
}
|
||||
},
|
||||
MONTHS(1, ChronoUnit.MONTHS) {
|
||||
@Override
|
||||
protected LocalDateTime doTruncateTo(LocalDateTime time) {
|
||||
return time.truncatedTo(ChronoUnit.DAYS)
|
||||
.withDayOfMonth(1);
|
||||
}
|
||||
},
|
||||
//季度
|
||||
QUARTER(3, ChronoUnit.MONTHS) {
|
||||
@Override
|
||||
protected LocalDateTime doTruncateTo(LocalDateTime time) {
|
||||
return time
|
||||
.withMonth(time.getMonth().firstMonthOfQuarter().getValue())
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.withDayOfMonth(1);
|
||||
|
||||
}
|
||||
},
|
||||
YEARS(1, ChronoUnit.YEARS) {
|
||||
@Override
|
||||
protected LocalDateTime doTruncateTo(LocalDateTime time) {
|
||||
return time.truncatedTo(ChronoUnit.DAYS)
|
||||
.withDayOfYear(1);
|
||||
}
|
||||
},
|
||||
//不分区,永远返回0
|
||||
FOREVER(1, ChronoUnit.FOREVER) {
|
||||
@Override
|
||||
public long truncatedTo(long timestamp) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Long> iterate(long from, long to, int duration) {
|
||||
return () -> new Iterator<Long>() {
|
||||
private boolean nexted;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return !nexted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long next() {
|
||||
nexted = true;
|
||||
return 0L;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@Getter
|
||||
private final int durationOfUnit;
|
||||
|
||||
@Getter
|
||||
private final ChronoUnit unit;
|
||||
|
||||
protected LocalDateTime doTruncateTo(LocalDateTime time) {
|
||||
return time.truncatedTo(unit);
|
||||
}
|
||||
|
||||
protected LocalDateTime next(LocalDateTime time, int duration) {
|
||||
return time.plus((long) durationOfUnit * duration, unit);
|
||||
}
|
||||
|
||||
protected long next(long timestamp) {
|
||||
return next(timestamp, 1);
|
||||
}
|
||||
|
||||
protected long next(long timestamp, int duration) {
|
||||
return this
|
||||
.next(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC), duration)
|
||||
.toInstant(ZoneOffset.UTC)
|
||||
.toEpochMilli();
|
||||
}
|
||||
|
||||
public long truncatedTo(long timestamp) {
|
||||
return this
|
||||
.doTruncateTo(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC))
|
||||
.toInstant(ZoneOffset.UTC)
|
||||
.toEpochMilli();
|
||||
}
|
||||
|
||||
public final long truncatedTo(long timestamp, int duration) {
|
||||
long ts = truncatedTo(timestamp);
|
||||
//指定了多个周期
|
||||
if (Math.abs(duration) > 1) {
|
||||
ts = next(ts, duration);
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
public long toMillis(int duration) {
|
||||
return duration * durationOfUnit * unit.getDuration().toMillis();
|
||||
}
|
||||
|
||||
public final Iterable<Long> iterate(long from, long to) {
|
||||
return iterate(from, to, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迭代时间区间的每一个周期时间
|
||||
*
|
||||
* @param from 时间从
|
||||
* @param to 时间止
|
||||
* @param duration 间隔数量,比如 2天为一个间隔
|
||||
* @return 每个间隔的时间戳迭代器
|
||||
*/
|
||||
public Iterable<Long> iterate(long from, long to, int duration) {
|
||||
return () -> new Iterator<Long>() {
|
||||
long _from = truncatedTo(Math.min(from, to));
|
||||
final long _to = truncatedTo(Math.max(from, to));
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return _from <= _to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long next() {
|
||||
long that = truncatedTo(_from, duration);
|
||||
_from = IntervalUnit.this.next(_from, duration);
|
||||
return that;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import org.hswebframework.web.exception.I18nSupportException;
|
||||
|
||||
public class JvmErrorException extends I18nSupportException {
|
||||
|
||||
public JvmErrorException(Throwable cause) {
|
||||
super("error.jvm_error",cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetlinks.core.utils.SerializeUtils;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
|
||||
/**
|
||||
* 描述,用于对某些操作的通用描述.
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.0
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
public class Operation implements Externalizable {
|
||||
/**
|
||||
* 操作来源
|
||||
*/
|
||||
private OperationSource source;
|
||||
|
||||
/**
|
||||
* 操作类型,比如: transparent-codec等
|
||||
*/
|
||||
private OperationType type;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type.getId() + "(" + type.getName() + "):[" + source.getId() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
source.writeExternal(out);
|
||||
SerializeUtils.writeObject(type.getId(), out);
|
||||
SerializeUtils.writeObject(type.getName(), out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
source = new OperationSource();
|
||||
source.readExternal(in);
|
||||
type = new DynamicOperationType((String) SerializeUtils.readObject(in), (String) SerializeUtils.readObject(in));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetlinks.core.utils.SerializeUtils;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.context.ContextView;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.Optional;
|
||||
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class OperationSource implements Externalizable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID,type对应操作的唯一标识
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 操作源名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 操作目标,通常为ID对应的详情数据
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
public static OperationSource of(String id, Object data) {
|
||||
return of(id, id, data);
|
||||
}
|
||||
|
||||
public static Context ofContext(String id, String name, Object data) {
|
||||
return Context.of(OperationSource.class, of(id, name, data));
|
||||
}
|
||||
|
||||
public static Optional<OperationSource> fromContext(ContextView ctx) {
|
||||
return ctx.getOrEmpty(OperationSource.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeUTF(id);
|
||||
SerializeUtils.writeObject(name, out);
|
||||
SerializeUtils.writeObject(data, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
id = in.readUTF();
|
||||
name = (String) SerializeUtils.readObject(in);
|
||||
data = SerializeUtils.readObject(in);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
public interface OperationType {
|
||||
String getId();
|
||||
|
||||
String getName();
|
||||
|
||||
static OperationType of(String id,String name){
|
||||
return new DynamicOperationType(id,name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import lombok.Generated;
|
||||
import org.hswebframework.web.id.IDGenerator;
|
||||
import org.jetlinks.core.config.ConfigKey;
|
||||
import org.jetlinks.core.message.HeaderKey;
|
||||
import org.springframework.core.ResolvableType;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author wangzheng
|
||||
* @since 1.0
|
||||
*/
|
||||
@Generated
|
||||
public interface PropertyConstants {
|
||||
//机构ID
|
||||
Key<String> orgId = Key.of("orgId");
|
||||
|
||||
//设备名称
|
||||
Key<String> deviceName = Key.of("deviceName");
|
||||
|
||||
//产品名称
|
||||
Key<String> productName = Key.of("productName");
|
||||
|
||||
//产品ID
|
||||
Key<String> productId = Key.of("productId");
|
||||
|
||||
/**
|
||||
* 关系信息.值格式:
|
||||
* <pre>{@code
|
||||
* [{"type":"user","id":"userId","rel":"manager"}]
|
||||
* }</pre>
|
||||
*/
|
||||
Key<List<Map<String, Object>>> relations = Key.of("relations");
|
||||
|
||||
//分组ID
|
||||
Key<List<String>> groupId = Key.of("groupId");
|
||||
|
||||
//是否记录task记录
|
||||
Key<Boolean> useTask = Key.of("useTask", false);
|
||||
|
||||
//taskId
|
||||
Key<String> taskId = Key.of("taskId");
|
||||
|
||||
//最大重试次数
|
||||
Key<Long> maxRetryTimes = Key.of("maxRetryTimes", () -> Long.getLong("device.message.task.retryTimes", 1), Long.class);
|
||||
//当前重试次数
|
||||
Key<Long> retryTimes = Key.of("retryTimes", () -> 0L, Long.class);
|
||||
|
||||
//服务ID
|
||||
Key<String> serverId = Key.of("serverId");
|
||||
|
||||
//全局唯一ID
|
||||
Key<String> uid = Key.of("_uid", IDGenerator.RANDOM::generate);
|
||||
|
||||
//设备接入网关ID
|
||||
Key<String> accessId = Key.of("accessId");
|
||||
|
||||
/**
|
||||
* 设备接入方式
|
||||
*
|
||||
* @see org.jetlinks.community.gateway.supports.DeviceGatewayProvider#getId
|
||||
*/
|
||||
Key<String> accessProvider = Key.of("accessProvider");
|
||||
|
||||
//设备创建者
|
||||
Key<String> creatorId = Key.of("creatorId");
|
||||
|
||||
@SuppressWarnings("all")
|
||||
static <T> Optional<T> getFromMap(ConfigKey<T> key, Map<String, Object> map) {
|
||||
return Optional.ofNullable((T) map.get(key.getKey()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
static <T> T getFromMapOrElse(ConfigKey<T> key, Map<String, Object> map, Supplier<T> defaultIfEmpty) {
|
||||
Object value = map.get(key.getKey());
|
||||
if (value == null) {
|
||||
return defaultIfEmpty.get();
|
||||
}
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@Generated
|
||||
interface Key<V> extends ConfigKey<V>, HeaderKey<V> {
|
||||
|
||||
@Override
|
||||
default Type getValueType() {
|
||||
return ConfigKey.super.getValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Class<V> getType() {
|
||||
return ConfigKey.super.getType();
|
||||
}
|
||||
|
||||
static <T> Key<T> of(String key) {
|
||||
return new Key<T>() {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefaultValue() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> Key<T> of(String key, T defaultValue) {
|
||||
return new Key<T>() {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> Key<T> of(String key, Supplier<T> defaultValue) {
|
||||
return new Key<T>() {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefaultValue() {
|
||||
return defaultValue == null ? null : defaultValue.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> Key<T> of(String key, Supplier<T> defaultValue, Type type) {
|
||||
return new Key<T>() {
|
||||
|
||||
@Override
|
||||
public Type getValueType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefaultValue() {
|
||||
return defaultValue == null ? null : defaultValue.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import org.jetlinks.community.utils.ConverterUtils;
|
||||
import org.jetlinks.core.config.ConfigKey;
|
||||
import org.jetlinks.core.message.DeviceMessage;
|
||||
import org.jetlinks.core.message.HeaderKey;
|
||||
import org.jetlinks.core.metadata.PropertyMetadata;
|
||||
import org.jetlinks.reactor.ql.utils.CastUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface PropertyMetadataConstants {
|
||||
|
||||
|
||||
/**
|
||||
* 属性来源
|
||||
*/
|
||||
interface Source {
|
||||
|
||||
//数据来源
|
||||
String id = "source";
|
||||
|
||||
HeaderKey<String> headerKey = HeaderKey.of(id, null);
|
||||
|
||||
//手动写值
|
||||
String manual = "manual";
|
||||
|
||||
//规则,虚拟属性
|
||||
String rule = "rule";
|
||||
|
||||
static boolean isManual(DeviceMessage message) {
|
||||
return message
|
||||
.getHeader(Source.headerKey)
|
||||
.map(Source.manual::equals)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
static void setManual(DeviceMessage message) {
|
||||
message.addHeader(headerKey, manual);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断属性是否手动赋值
|
||||
*
|
||||
* @param metadata 属性物模型
|
||||
* @return 是否手动赋值
|
||||
*/
|
||||
static boolean isManual(PropertyMetadata metadata) {
|
||||
return metadata.getExpand(id)
|
||||
.map(manual::equals)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性读写模式
|
||||
*/
|
||||
interface AccessMode {
|
||||
String id = "type";
|
||||
|
||||
//读
|
||||
String read = "read";
|
||||
//写
|
||||
String write = "write";
|
||||
//上报
|
||||
String report = "report";
|
||||
|
||||
static boolean isRead(PropertyMetadata property) {
|
||||
return property
|
||||
.getExpand(id)
|
||||
.map(val -> val.toString().contains(read))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
static boolean isWrite(PropertyMetadata property) {
|
||||
return property
|
||||
.getExpand(id)
|
||||
.map(val -> val.toString().contains(write))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
static boolean isReport(PropertyMetadata property) {
|
||||
return property
|
||||
.getExpand(id)
|
||||
.map(val -> val.toString().contains(report))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
|
||||
interface Metrics {
|
||||
String id = "metrics";
|
||||
|
||||
|
||||
static Map<String, Object> metricsToExpands(List<PropertyMetric> metrics) {
|
||||
return Collections.singletonMap(id, metrics);
|
||||
}
|
||||
|
||||
static List<PropertyMetric> getMetrics(PropertyMetadata metadata) {
|
||||
return metadata
|
||||
.getExpand(id)
|
||||
.map(obj -> ConverterUtils.convertToList(obj, PropertyMetric::of))
|
||||
.orElseGet(Collections::emptyList);
|
||||
}
|
||||
|
||||
static Optional<PropertyMetric> getMetric(PropertyMetadata metadata, String metric) {
|
||||
return metadata
|
||||
.getExpand(id)
|
||||
.map(obj -> {
|
||||
for (PropertyMetric propertyMetric : ConverterUtils.convertToList(obj, PropertyMetric::of)) {
|
||||
if (Objects.equals(metric, propertyMetric.getId())) {
|
||||
return propertyMetric;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Group {
|
||||
ConfigKey<String> id = ConfigKey.of("groupId", "分组ID", String.class);
|
||||
ConfigKey<String> name = ConfigKey.of("groupName", "分组名称", String.class);
|
||||
|
||||
|
||||
static String getId(PropertyMetadata metadata) {
|
||||
return metadata
|
||||
.getExpand(id)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
static String getName(PropertyMetadata metadata) {
|
||||
return metadata
|
||||
.getExpand(name)
|
||||
.orElse("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.community.utils.ConverterUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PropertyMetric {
|
||||
@Schema(description = "指标ID")
|
||||
@NotBlank
|
||||
private String id;
|
||||
|
||||
@Schema(description = "名称")
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@Schema(description = "值,范围值使用逗号分隔")
|
||||
private Object value;
|
||||
|
||||
@Schema(description = "是否为范围值")
|
||||
private boolean range;
|
||||
|
||||
@Schema(description = "其他拓展配置")
|
||||
private Map<String, Object> expands;
|
||||
|
||||
public Object castValue() {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (range) {
|
||||
return ConverterUtils.tryConvertToList(value, Function.identity());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public PropertyMetric merge(PropertyMetric another) {
|
||||
if (!StringUtils.hasText(this.name)) {
|
||||
this.setValue(another.value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public static PropertyMetric of(String id, String name, Object value) {
|
||||
PropertyMetric metric = new PropertyMetric();
|
||||
metric.setId(id);
|
||||
metric.setName(name);
|
||||
metric.setValue(value);
|
||||
return metric;
|
||||
}
|
||||
|
||||
public static PropertyMetric of(Object mapMetric) {
|
||||
return FastBeanCopier.copy(mapMetric, new PropertyMetric());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Iterator;
|
||||
|
||||
public interface TimerIterable {
|
||||
|
||||
Iterator<ZonedDateTime> iterator(ZonedDateTime baseTime);
|
||||
|
||||
}
|
||||
|
|
@ -1,876 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import com.cronutils.builder.CronBuilder;
|
||||
import com.cronutils.model.Cron;
|
||||
import com.cronutils.model.definition.CronConstraintsFactory;
|
||||
import com.cronutils.model.definition.CronDefinition;
|
||||
import com.cronutils.model.definition.CronDefinitionBuilder;
|
||||
import com.cronutils.model.field.expression.FieldExpression;
|
||||
import com.cronutils.model.field.expression.FieldExpressionFactory;
|
||||
import com.cronutils.model.time.ExecutionTime;
|
||||
import com.cronutils.parser.CronParser;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.hswebframework.ezorm.core.param.Term;
|
||||
import org.hswebframework.web.exception.ValidationException;
|
||||
import org.hswebframework.web.i18n.LocaleUtils;
|
||||
import org.jetlinks.community.terms.I18nSpec;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.time.*;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class TimerSpec implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "触发方式")
|
||||
@NotNull
|
||||
private Trigger trigger;
|
||||
|
||||
@Schema(description = "使用日程标签进行触发")
|
||||
private Set<String> scheduleTags;
|
||||
|
||||
//Cron表达式
|
||||
@Schema(description = "触发方式为[cron]时不能为空")
|
||||
private String cron;
|
||||
|
||||
@Schema(description = "执行的时间.为空则表示每天,触发方式为[week]则为1-7,触发方式为[month]时则为1-31")
|
||||
private Set<Integer> when;
|
||||
|
||||
@Schema(description = "执行模式,一次还是周期执行")
|
||||
private ExecuteMod mod;
|
||||
|
||||
@Schema(description = "执行模式为[period]时不能为空")
|
||||
private Period period;
|
||||
|
||||
@Schema(description = "执行模式为[period]时不能与period同时为空")
|
||||
private List<Period> periods;
|
||||
|
||||
@Schema(description = "执行模式为[once]时不能为空")
|
||||
private Once once;
|
||||
|
||||
@Schema(description = "组合触发配置列表")
|
||||
private Multi multi;
|
||||
|
||||
public static TimerSpec cron(String cron) {
|
||||
TimerSpec spec = new TimerSpec();
|
||||
spec.cron = cron;
|
||||
spec.trigger = Trigger.cron;
|
||||
return spec;
|
||||
}
|
||||
|
||||
public List<Period> periods() {
|
||||
List<Period> list = new ArrayList<>(1);
|
||||
if (periods != null) {
|
||||
list.addAll(periods);
|
||||
}
|
||||
if (period != null) {
|
||||
list.add(period);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Predicate<LocalDateTime> createRangeFilter() {
|
||||
if (CollectionUtils.isEmpty(when)) {
|
||||
return ignore -> true;
|
||||
}
|
||||
if (trigger == Trigger.week) {
|
||||
return date -> when.contains(date.getDayOfWeek().getValue());
|
||||
} else if (trigger == Trigger.month) {
|
||||
return date -> when.contains(date.getDayOfMonth());
|
||||
}
|
||||
return ignore -> true;
|
||||
}
|
||||
|
||||
public Predicate<LocalDateTime> createTimeFilter() {
|
||||
Predicate<LocalDateTime> range = createRangeFilter();
|
||||
if (mod == ExecuteMod.period) {
|
||||
Predicate<LocalDateTime> predicate = null;
|
||||
//可能多个周期
|
||||
for (Period period : periods()) {
|
||||
//周期执行指定了to,表示只在时间范围段内执行
|
||||
LocalTime to = period.toLocalTime();
|
||||
LocalTime from = period.fromLocalTime();
|
||||
Predicate<LocalDateTime> _predicate = time -> !time.toLocalTime().isBefore(from);
|
||||
if (to != null) {
|
||||
_predicate = _predicate.and(time -> !time.toLocalTime().isAfter(to));
|
||||
}
|
||||
if (predicate == null) {
|
||||
predicate = _predicate;
|
||||
} else {
|
||||
predicate = _predicate.or(predicate);
|
||||
}
|
||||
}
|
||||
if (predicate == null) {
|
||||
return range;
|
||||
}
|
||||
return predicate.and(range);
|
||||
}
|
||||
if (mod == ExecuteMod.once) {
|
||||
LocalTime onceTime = once.localTime();
|
||||
Predicate<LocalDateTime> predicate
|
||||
= time -> compareOnceTime(time.toLocalTime(), onceTime) == 0;
|
||||
return predicate.and(range);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
public int compareOnceTime(LocalTime time1, LocalTime time2) {
|
||||
int cmp = Integer.compare(time1.getHour(), time2.getHour());
|
||||
if (cmp == 0) {
|
||||
cmp = Integer.compare(time1.getMinute(), time2.getMinute());
|
||||
if (cmp == 0) {
|
||||
cmp = Integer.compare(time1.getSecond(), time2.getSecond());
|
||||
//不比较纳秒
|
||||
}
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
public String toCronExpression() {
|
||||
return toCron().asString();
|
||||
}
|
||||
|
||||
private static CronDefinition quartz() {
|
||||
return CronDefinitionBuilder
|
||||
.defineCron()
|
||||
.withSeconds()
|
||||
.withValidRange(0, 59)
|
||||
.and()
|
||||
.withMinutes()
|
||||
.withValidRange(0, 59)
|
||||
.and()
|
||||
.withHours()
|
||||
.withValidRange(0, 23)
|
||||
.and()
|
||||
.withDayOfMonth()
|
||||
.withValidRange(1, 31)
|
||||
.supportsL()
|
||||
.supportsW()
|
||||
.supportsLW()
|
||||
.supportsQuestionMark()
|
||||
.and()
|
||||
.withMonth()
|
||||
.withValidRange(1, 12)
|
||||
.and()
|
||||
.withDayOfWeek()
|
||||
.withValidRange(1, 7)
|
||||
.withMondayDoWValue(1)
|
||||
.supportsHash()
|
||||
.supportsL()
|
||||
.supportsQuestionMark()
|
||||
.and()
|
||||
.withYear()
|
||||
.withValidRange(1970, 2099)
|
||||
.withStrictRange()
|
||||
.optional()
|
||||
.and()
|
||||
.withCronValidation(CronConstraintsFactory.ensureEitherDayOfWeekOrDayOfMonth())
|
||||
.instance();
|
||||
}
|
||||
|
||||
public Cron toCron() {
|
||||
CronDefinition definition = quartz();
|
||||
if (trigger == Trigger.cron || trigger == null) {
|
||||
Assert.hasText(cron, "error.scene_rule_timer_cron_cannot_be_empty");
|
||||
return new CronParser(definition).parse(cron).validate();
|
||||
}
|
||||
|
||||
CronBuilder builder = CronBuilder.cron(definition);
|
||||
builder.withYear(FieldExpression.always());
|
||||
builder.withMonth(FieldExpression.always());
|
||||
|
||||
FieldExpression range;
|
||||
if (CollectionUtils.isNotEmpty(when)) {
|
||||
FieldExpression expr = null;
|
||||
for (Integer integer : when) {
|
||||
if (expr == null) {
|
||||
expr = FieldExpressionFactory.on(integer);
|
||||
} else {
|
||||
expr = expr.and(FieldExpressionFactory.on(integer));
|
||||
}
|
||||
}
|
||||
range = expr;
|
||||
} else {
|
||||
range = FieldExpressionFactory.questionMark();
|
||||
}
|
||||
|
||||
if (trigger == Trigger.week) {
|
||||
builder.withDoM(FieldExpressionFactory.questionMark())
|
||||
.withDoW(range);
|
||||
} else if (trigger == Trigger.month) {
|
||||
builder.withDoM(range)
|
||||
.withDoW(FieldExpressionFactory.questionMark());
|
||||
}
|
||||
|
||||
//执行一次
|
||||
if (mod == ExecuteMod.once) {
|
||||
LocalTime time = once.localTime();
|
||||
builder.withHour(FieldExpressionFactory.on(time.getHour()));
|
||||
builder.withMinute(FieldExpressionFactory.on(time.getMinute()));
|
||||
builder.withSecond(FieldExpressionFactory.on(time.getSecond()));
|
||||
}
|
||||
//周期执行
|
||||
if (mod == ExecuteMod.period) {
|
||||
LocalTime time = period.fromLocalTime();
|
||||
PeriodUnit unit = period.unit;
|
||||
if (unit == PeriodUnit.hours) {
|
||||
builder.withHour(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getHour()), period.every))
|
||||
.withMinute(FieldExpressionFactory.on(time.getMinute()))
|
||||
.withSecond(FieldExpressionFactory.on(time.getSecond()));
|
||||
} else if (unit == PeriodUnit.minutes) {
|
||||
builder
|
||||
.withHour(FieldExpressionFactory.always())
|
||||
.withMinute(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getMinute()), period.every))
|
||||
.withSecond(FieldExpressionFactory.on(time.getSecond()));
|
||||
} else if (unit == PeriodUnit.seconds) {
|
||||
builder
|
||||
.withHour(FieldExpressionFactory.always())
|
||||
.withMinute(FieldExpressionFactory.always())
|
||||
.withSecond(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getSecond()), period.every));
|
||||
}
|
||||
}
|
||||
return builder.instance().validate();
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
if (trigger == null) {
|
||||
Assert.hasText(cron, "error.scene_rule_timer_cron_cannot_be_empty");
|
||||
}
|
||||
if (trigger == Trigger.cron) {
|
||||
try {
|
||||
toCronExpression();
|
||||
} catch (Throwable e) {
|
||||
ValidationException exception = new ValidationException("cron", "error.cron_format_error", cron);
|
||||
exception.addSuppressed(e);
|
||||
throw exception;
|
||||
}
|
||||
} else if (trigger == Trigger.multi) {
|
||||
List<TimerSpec> multiSpec = multi.getSpec();
|
||||
if (CollectionUtils.isNotEmpty(multiSpec)) {
|
||||
for (TimerSpec spec : multiSpec) {
|
||||
spec.validate();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nextDurationBuilder().apply(ZonedDateTime.now());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
@NoArgsConstructor
|
||||
public static class Once implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
//时间点
|
||||
@Schema(description = "时间点.格式:[hh:mm],或者[hh:mm:ss]")
|
||||
@NotBlank
|
||||
private String time;
|
||||
|
||||
public LocalTime localTime() {
|
||||
return parsTime(time);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Period implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
//周期执行的时间区间
|
||||
@Schema(description = "执行时间范围从.格式:[hh:mm],或者[hh:mm:ss]")
|
||||
private String from;
|
||||
@Schema(description = "执行时间范围止.格式:[hh:mm],或者[hh:mm:ss]")
|
||||
private String to;
|
||||
|
||||
@Schema(description = "周期值,如:每[every][unit]执行一次")
|
||||
private int every;
|
||||
|
||||
@Schema(description = "周期执行单位")
|
||||
private PeriodUnit unit;
|
||||
|
||||
public LocalTime fromLocalTime() {
|
||||
return parsTime(from);
|
||||
}
|
||||
|
||||
public LocalTime toLocalTime() {
|
||||
return parsTime(to);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Multi implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "组合触发配置列表")
|
||||
private List<TimerSpec> spec;
|
||||
|
||||
@Schema(description = "多个触发的关系。and、or")
|
||||
private Term.Type type = Term.Type.or;
|
||||
|
||||
@Schema(description = "组合触发时,只触发一次的最小时间间隔")
|
||||
private int timeSpanSecond = 2;
|
||||
}
|
||||
|
||||
private static LocalTime parsTime(String time) {
|
||||
return LocalTime.parse(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个下一次执行时间间隔构造器,通过构造器来获取基准时间间隔
|
||||
* <pre>{@code
|
||||
*
|
||||
* Function<ZonedDateTime, Duration> builder = nextDurationBuilder();
|
||||
*
|
||||
* Duration duration = builder.apply(ZonedDateTime.now());
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return 构造器
|
||||
*/
|
||||
public Function<ZonedDateTime, Duration> nextDurationBuilder() {
|
||||
return nextDurationBuilder(ZonedDateTime.now());
|
||||
}
|
||||
|
||||
|
||||
public Function<ZonedDateTime, Duration> nextDurationBuilder(ZonedDateTime baseTime) {
|
||||
Iterator<ZonedDateTime> it = iterable().iterator(baseTime);
|
||||
return (time) -> {
|
||||
Duration duration;
|
||||
do {
|
||||
duration = Duration.between(time, time = it.next());
|
||||
}
|
||||
while (duration.toMillis() < 0);
|
||||
return duration;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个时间构造器,通过构造器来获取下一次时间
|
||||
* <pre>{@code
|
||||
*
|
||||
* Function<ZonedDateTime, ZonedDateTime> builder = nextTimeBuilder();
|
||||
*
|
||||
* ZonedDateTime nextTime = builder.apply(ZonedDateTime.now());
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return 构造器
|
||||
*/
|
||||
public Function<ZonedDateTime, ZonedDateTime> nextTimeBuilder() {
|
||||
TimerIterable it = iterable();
|
||||
return time -> it.iterator(time).next();
|
||||
}
|
||||
|
||||
static int MAX_IT_TIMES = 10000;
|
||||
|
||||
private TimerIterable cronIterable() {
|
||||
Cron cron = this.toCron();
|
||||
ExecutionTime executionTime = ExecutionTime.forCron(cron);
|
||||
Predicate<LocalDateTime> filter = createTimeFilter();
|
||||
return baseTime -> new Iterator<ZonedDateTime>() {
|
||||
ZonedDateTime current = baseTime;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return current != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
ZonedDateTime dateTime = current;
|
||||
int i = 0;
|
||||
do {
|
||||
dateTime = executionTime
|
||||
.nextExecution(dateTime)
|
||||
.orElse(null);
|
||||
if (dateTime == null) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (filter.test(dateTime.toLocalDateTime())) {
|
||||
break;
|
||||
}
|
||||
} while (i < MAX_IT_TIMES);
|
||||
return current = dateTime;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private TimerIterable periodIterable() {
|
||||
List<Period> periods = periods();
|
||||
Assert.notEmpty(periods, "period or periods can not be null");
|
||||
Predicate<LocalDateTime> filter = createTimeFilter();
|
||||
|
||||
Duration _Duration = null;
|
||||
LocalTime earliestFrom = LocalTime.MAX;
|
||||
LocalTime latestTo = LocalTime.MIN;
|
||||
|
||||
//使用最小的执行周期进行判断?
|
||||
for (Period period : periods) {
|
||||
Duration duration = Duration.of(period.every, period.unit.temporal);
|
||||
LocalTime from = period.fromLocalTime();
|
||||
LocalTime to = period.toLocalTime();
|
||||
|
||||
// 更新最小的duration
|
||||
if (_Duration == null || duration.compareTo(_Duration) < 0) {
|
||||
_Duration = duration;
|
||||
}
|
||||
|
||||
// 更新最早的起始时间
|
||||
if (from != null && from.isBefore(earliestFrom)) {
|
||||
earliestFrom = from;
|
||||
}
|
||||
|
||||
// 更新最晚的结束时间
|
||||
if (to != null && to.isAfter(latestTo)) {
|
||||
latestTo = to;
|
||||
}
|
||||
}
|
||||
|
||||
Duration duration = _Duration;
|
||||
LocalTime firstFrom = earliestFrom.equals(LocalTime.MAX) ? LocalTime.MIDNIGHT : earliestFrom;
|
||||
LocalTime endTo = latestTo.equals(LocalTime.MIN) ? null : latestTo;
|
||||
|
||||
return baseTime -> new Iterator<ZonedDateTime>() {
|
||||
ZonedDateTime current = baseTime;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime next() {
|
||||
ZonedDateTime dateTime = current;
|
||||
int max = MAX_IT_TIMES;
|
||||
do {
|
||||
dateTime = dateTime.plus(duration);
|
||||
// 检查时间是否在 firstFrom 和 endTo 之间
|
||||
LocalTime time = dateTime.toLocalTime();
|
||||
if (time.isBefore(firstFrom) || time.isAfter(endTo)) {
|
||||
// 获取第二天的 firstFrom
|
||||
ZonedDateTime nextDayFromTime = dateTime.toLocalDate().plusDays(1).atTime(firstFrom).atZone(dateTime.getZone());
|
||||
|
||||
// 计算当前时间到 nextDayFromTime 的差值
|
||||
Duration timeDifference = Duration.between(dateTime, nextDayFromTime);
|
||||
|
||||
// 计算可以整除的 duration 数量
|
||||
long n = timeDifference.toMillis() / duration.toMillis();
|
||||
|
||||
// 跳转到下一个 n * duration 的时间点
|
||||
dateTime = dateTime.plus(n * duration.toMillis(), ChronoUnit.MILLIS);
|
||||
}
|
||||
if (filter.test(dateTime.toLocalDateTime())) {
|
||||
break;
|
||||
}
|
||||
max--;
|
||||
} while (max > 0);
|
||||
|
||||
return current = dateTime;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private TimerIterable onceIterable() {
|
||||
Assert.notNull(once, "once can not be null");
|
||||
Predicate<LocalDateTime> filter = createTimeFilter();
|
||||
LocalTime onceTime = once.localTime();
|
||||
return baseTime -> new Iterator<ZonedDateTime>() {
|
||||
ZonedDateTime current = baseTime;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime next() {
|
||||
ZonedDateTime dateTime = current;
|
||||
int max = MAX_IT_TIMES;
|
||||
if (!dateTime.toLocalTime().equals(onceTime)) {
|
||||
dateTime = onceTime.atDate(dateTime.toLocalDate()).atZone(dateTime.getZone());
|
||||
}
|
||||
do {
|
||||
if (filter.test(dateTime.toLocalDateTime()) && current.compareTo(dateTime) <= 0) {
|
||||
current = dateTime.plusDays(1);
|
||||
break;
|
||||
}
|
||||
dateTime = dateTime.plusDays(1);
|
||||
max--;
|
||||
} while (max > 0);
|
||||
return dateTime;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private TimerIterable multiSpecIterable() {
|
||||
List<TimerSpec> multiSpec = multi.getSpec();
|
||||
Assert.notEmpty(multiSpec, "multiSpec can not be empty");
|
||||
return baseTime -> new Iterator<ZonedDateTime>() {
|
||||
final List<ZonedDateTime> timeList = new ArrayList<>(multiSpec.size());
|
||||
|
||||
final List<Iterator<ZonedDateTime>> iterators = multiSpec
|
||||
.stream()
|
||||
.map(spec -> spec.iterable().iterator(baseTime))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
switch (multi.getType()) {
|
||||
case and:
|
||||
return iterators.stream().allMatch(Iterator::hasNext);
|
||||
case or:
|
||||
return iterators.stream().anyMatch(Iterator::hasNext);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime next() {
|
||||
switch (multi.getType()) {
|
||||
case and:
|
||||
return handleNextAnd();
|
||||
case or:
|
||||
return handleNextOr();
|
||||
default:
|
||||
return baseTime;
|
||||
}
|
||||
}
|
||||
|
||||
private ZonedDateTime handleNextAnd() {
|
||||
ZonedDateTime dateTime = null;
|
||||
int max = MAX_IT_TIMES;
|
||||
int match = 0;
|
||||
do {
|
||||
for (Iterator<ZonedDateTime> iterator : iterators) {
|
||||
ZonedDateTime next = iterator.next();
|
||||
if (dateTime == null) {
|
||||
dateTime = next;
|
||||
}
|
||||
// 若生成的时间比当前选中的时间早,则继续生成
|
||||
while (next.isBefore(dateTime)) {
|
||||
next = iterator.next();
|
||||
}
|
||||
if (next.isEqual(dateTime)) {
|
||||
// 所有定时器的next时间一致时,返回时间
|
||||
if (++match == iterators.size()) {
|
||||
return dateTime;
|
||||
}
|
||||
} else {
|
||||
dateTime = next;
|
||||
}
|
||||
}
|
||||
max--;
|
||||
} while (
|
||||
max > 0
|
||||
);
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
private ZonedDateTime handleNextOr() {
|
||||
ZonedDateTime earliest = null;
|
||||
// 每个定时器生成next
|
||||
fillTimeList();
|
||||
|
||||
// 获取最早的一个时间
|
||||
for (ZonedDateTime zonedDateTime : timeList) {
|
||||
if (earliest == null || earliest.isAfter(zonedDateTime) || earliest.isEqual(zonedDateTime)) {
|
||||
earliest = zonedDateTime;
|
||||
}
|
||||
}
|
||||
// 清空被选中的最早时间
|
||||
for (int i = 0; i < timeList.size(); i++) {
|
||||
if (timeList.get(i).isEqual(earliest)) {
|
||||
timeList.set(i, null);
|
||||
}
|
||||
}
|
||||
return earliest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历所有定时器,若有next为空的则生成新的
|
||||
*/
|
||||
private void fillTimeList() {
|
||||
for (int i = 0; i < iterators.size(); i++) {
|
||||
if (timeList.size() <= i) {
|
||||
timeList.add(iterators.get(i).next());
|
||||
} else if (timeList.get(i) == null) {
|
||||
timeList.set(i, iterators.get(i).next());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public TimerIterable iterable() {
|
||||
if (trigger == Trigger.multi) {
|
||||
return multiSpecIterable();
|
||||
}
|
||||
if ((trigger == Trigger.cron || trigger == null) && cron != null) {
|
||||
return cronIterable();
|
||||
}
|
||||
return mod == ExecuteMod.period ? periodIterable() : onceIterable();
|
||||
}
|
||||
|
||||
public List<ZonedDateTime> getNextExecuteTimes(ZonedDateTime from, long times) {
|
||||
List<ZonedDateTime> timeList = new ArrayList<>((int) times);
|
||||
Iterator<ZonedDateTime> it = iterable().iterator(from);
|
||||
for (long i = 0; i < times; i++) {
|
||||
timeList.add(it.next());
|
||||
}
|
||||
return timeList;
|
||||
}
|
||||
|
||||
public Flux<Long> flux() {
|
||||
return flux(Schedulers.parallel());
|
||||
}
|
||||
|
||||
public Flux<Long> flux(Scheduler scheduler) {
|
||||
return new TimerFlux(nextDurationBuilder(), scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (getTrigger() == null) {
|
||||
return null;
|
||||
}
|
||||
switch (getTrigger()) {
|
||||
case week: {
|
||||
return weekDesc();
|
||||
}
|
||||
case month: {
|
||||
return monthDesc();
|
||||
}
|
||||
case cron: {
|
||||
return getCron();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String weekDesc() {
|
||||
I18nSpec spec = new I18nSpec();
|
||||
spec.setCode("message.timer_spec_desc");
|
||||
List<I18nSpec> args = new ArrayList<>();
|
||||
if (when == null || when.isEmpty()) {
|
||||
args.add(I18nSpec.of("message.timer_spec_desc_everyday", "每天"));
|
||||
} else {
|
||||
String week = when
|
||||
.stream()
|
||||
.map(weekNum -> LocaleUtils.resolveMessage("message.timer_spec_desc_week_" + weekNum))
|
||||
.collect(Collectors.joining(LocaleUtils.resolveMessage("message.timer_spec_desc_seperator")));
|
||||
args.add(I18nSpec.of(
|
||||
"message.timer_spec_desc_everyweek",
|
||||
"每周" + week,
|
||||
week));
|
||||
}
|
||||
args.add(timerModDesc());
|
||||
spec.setArgs(args);
|
||||
return spec.resolveI18nMessage();
|
||||
}
|
||||
|
||||
private String monthDesc() {
|
||||
I18nSpec spec = new I18nSpec();
|
||||
spec.setCode("message.timer_spec_desc");
|
||||
List<I18nSpec> args = new ArrayList<>();
|
||||
if (when == null || when.isEmpty()) {
|
||||
args.add(I18nSpec.of("message.timer_spec_desc_everyday", "每天"));
|
||||
} else {
|
||||
String month = when
|
||||
.stream()
|
||||
.map(monthNum -> {
|
||||
switch (monthNum) {
|
||||
case 1:
|
||||
return LocaleUtils.resolveMessage("message.timer_spec_desc_month_1", monthNum);
|
||||
case 2:
|
||||
return LocaleUtils.resolveMessage("message.timer_spec_desc_month_2", monthNum);
|
||||
case 3:
|
||||
return LocaleUtils.resolveMessage("message.timer_spec_desc_month_3", monthNum);
|
||||
default:
|
||||
return LocaleUtils.resolveMessage("message.timer_spec_desc_month", monthNum);
|
||||
}
|
||||
})
|
||||
.collect(Collectors.joining(LocaleUtils.resolveMessage("message.timer_spec_desc_seperator")));
|
||||
args.add(I18nSpec.of(
|
||||
"message.timer_spec_desc_everymonth",
|
||||
"每月" + month,
|
||||
month));
|
||||
}
|
||||
args.add(timerModDesc());
|
||||
spec.setArgs(args);
|
||||
return spec.resolveI18nMessage();
|
||||
}
|
||||
|
||||
private I18nSpec timerModDesc() {
|
||||
switch (getMod()) {
|
||||
case period: {
|
||||
if (getPeriod() == null) {
|
||||
break;
|
||||
}
|
||||
return I18nSpec.of(
|
||||
"message.timer_spec_desc_period",
|
||||
getPeriod().getFrom() + "-" + getPeriod().getTo() +
|
||||
" 每" + getPeriod().getEvery() + getPeriod().getUnit().name(),
|
||||
I18nSpec.of("message.timer_spec_desc_period_duration",
|
||||
getPeriod().getFrom() + "-" + getPeriod().getTo(),
|
||||
getPeriod().getFrom(),
|
||||
getPeriod().getTo()),
|
||||
getPeriod().getEvery(),
|
||||
I18nSpec.of("message.timer_spec_desc_period_" + getPeriod().getUnit().name(),
|
||||
getPeriod().getUnit().name())
|
||||
);
|
||||
}
|
||||
case once: {
|
||||
// [time],执行1次
|
||||
if (getOnce() == null) {
|
||||
break;
|
||||
}
|
||||
return I18nSpec.of("message.timer_spec_desc_period_once", getOnce().getTime(), getOnce().getTime());
|
||||
}
|
||||
}
|
||||
return I18nSpec.of("", "");
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class TimerFlux extends Flux<Long> {
|
||||
final Function<ZonedDateTime, Duration> spec;
|
||||
final Scheduler scheduler;
|
||||
|
||||
@Override
|
||||
public void subscribe(@Nonnull CoreSubscriber<? super Long> coreSubscriber) {
|
||||
|
||||
TimerSubscriber subscriber = new TimerSubscriber(spec, scheduler, coreSubscriber);
|
||||
coreSubscriber.onSubscribe(subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
static class TimerSubscriber implements Subscription {
|
||||
final Function<ZonedDateTime, Duration> spec;
|
||||
final CoreSubscriber<? super Long> subscriber;
|
||||
final Scheduler scheduler;
|
||||
long count;
|
||||
Disposable scheduling;
|
||||
|
||||
public TimerSubscriber(Function<ZonedDateTime, Duration> spec,
|
||||
Scheduler scheduler,
|
||||
CoreSubscriber<? super Long> subscriber) {
|
||||
this.scheduler = scheduler;
|
||||
this.spec = spec;
|
||||
this.subscriber = subscriber;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void request(long l) {
|
||||
trySchedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (scheduling != null) {
|
||||
scheduling.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void onNext() {
|
||||
|
||||
if (canSchedule()) {
|
||||
subscriber.onNext(count++);
|
||||
}
|
||||
trySchedule();
|
||||
}
|
||||
|
||||
void trySchedule() {
|
||||
if (scheduling != null) {
|
||||
scheduling.dispose();
|
||||
}
|
||||
|
||||
ZonedDateTime now = ZonedDateTime.ofInstant(Instant.ofEpochMilli(scheduler.now(TimeUnit.MILLISECONDS)), ZoneId.systemDefault());
|
||||
Duration delay = spec.apply(now);
|
||||
|
||||
scheduling = scheduler
|
||||
.schedule(
|
||||
this::onNext,
|
||||
delay.toMillis(),
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
}
|
||||
|
||||
protected boolean canSchedule() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public enum Trigger {
|
||||
//按周
|
||||
week,
|
||||
//按月
|
||||
month,
|
||||
//cron表达式
|
||||
cron,
|
||||
// 多个触发组合
|
||||
multi
|
||||
}
|
||||
|
||||
public enum ExecuteMod {
|
||||
period,
|
||||
once
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum PeriodUnit {
|
||||
seconds(ChronoUnit.SECONDS),
|
||||
minutes(ChronoUnit.MINUTES),
|
||||
hours(ChronoUnit.HOURS);
|
||||
private final TemporalUnit temporal;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,7 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.community.utils.TimeUtils;
|
||||
import org.jetlinks.reactor.ql.utils.CastUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -27,11 +11,11 @@ import java.util.Optional;
|
|||
|
||||
public interface ValueObject {
|
||||
|
||||
Map<String, Object> values();
|
||||
Map<String, Object> getAll();
|
||||
|
||||
default Optional<Object> get(String name) {
|
||||
return Optional.ofNullable(values())
|
||||
.map(map -> map.get(name));
|
||||
return Optional.ofNullable(getAll())
|
||||
.map(map -> map.get(name));
|
||||
}
|
||||
|
||||
default Optional<Integer> getInt(String name) {
|
||||
|
|
@ -60,8 +44,9 @@ public interface ValueObject {
|
|||
.map(Interval::of);
|
||||
}
|
||||
|
||||
default Interval getInterval(String name, Interval defaultValue) {
|
||||
return getInterval(name)
|
||||
default Interval getInterval(String name,Interval defaultValue) {
|
||||
return getString(name)
|
||||
.map(Interval::of)
|
||||
.orElse(defaultValue);
|
||||
}
|
||||
|
||||
|
|
@ -71,14 +56,9 @@ public interface ValueObject {
|
|||
}
|
||||
|
||||
default Optional<Date> getDate(String name) {
|
||||
return this
|
||||
.get(name)
|
||||
.map(d -> {
|
||||
if (d instanceof Date) {
|
||||
return (Date) d;
|
||||
}
|
||||
return TimeUtils.parseDate(String.valueOf(d));
|
||||
});
|
||||
return get(name)
|
||||
.map(String::valueOf)
|
||||
.map(TimeUtils::parseDate);
|
||||
}
|
||||
|
||||
default Date getDate(String name, Date defaultValue) {
|
||||
|
|
@ -103,8 +83,7 @@ public interface ValueObject {
|
|||
}
|
||||
|
||||
default Optional<Boolean> getBoolean(String name) {
|
||||
return get(name)
|
||||
.map(CastUtils::castBoolean);
|
||||
return get(name, Boolean.class);
|
||||
}
|
||||
|
||||
default boolean getBoolean(String name, boolean defaultValue) {
|
||||
|
|
@ -116,12 +95,7 @@ public interface ValueObject {
|
|||
.map(obj -> FastBeanCopier.DEFAULT_CONVERT.convert(obj, type, FastBeanCopier.EMPTY_CLASS_ARRAY));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static ValueObject of(Map<String, ?> mapVal) {
|
||||
return () -> (Map<String, Object>) mapVal;
|
||||
}
|
||||
|
||||
default <T> T as(Class<T> type) {
|
||||
return FastBeanCopier.copy(values(), type);
|
||||
static ValueObject of(Map<String, Object> mapVal) {
|
||||
return () -> mapVal;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,3 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community;
|
||||
|
||||
import lombok.Getter;
|
||||
|
|
@ -23,6 +8,6 @@ public class Version {
|
|||
|
||||
private final String edition = "community";
|
||||
|
||||
private final String version = "2.11.0-SNAPSHOT";
|
||||
private final String version = "1.3.0";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.annotation.command;
|
||||
|
||||
|
||||
import org.jetlinks.core.annotation.command.CommandHandler;
|
||||
import org.springframework.stereotype.Indexed;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 标记一个类为命令服务支持端点,用于对外提供命令支持
|
||||
* <pre>{@code
|
||||
*
|
||||
* @CommandService("myService")
|
||||
* public class MyCommandService{
|
||||
*
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 1.2.3
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Indexed
|
||||
public @interface CommandService {
|
||||
|
||||
/**
|
||||
* 服务标识
|
||||
*
|
||||
* @return 服务标识
|
||||
*/
|
||||
String id();
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*
|
||||
* @return 服务名称
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* 服务描述
|
||||
*
|
||||
* @return 服务描述
|
||||
*/
|
||||
String[] description() default {};
|
||||
|
||||
|
||||
/**
|
||||
* 是否根据注解扫描注册服务
|
||||
*
|
||||
* @return 是否注册服务
|
||||
*/
|
||||
boolean autoRegistered() default true;
|
||||
|
||||
/**
|
||||
* 命令定义,用于声明支持的命令
|
||||
*
|
||||
* @return 命令定义
|
||||
*/
|
||||
CommandHandler[] commands() default {};
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.authorize;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.DefaultDimensionType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthenticationSpec implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3512105446265694264L;
|
||||
|
||||
private RoleSpec role;
|
||||
|
||||
private List<PermissionSpec> permissions;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class RoleSpec {
|
||||
private List<String> idList;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class PermissionSpec implements Serializable {
|
||||
private static final long serialVersionUID = 7188197046015343251L;
|
||||
private String id;
|
||||
private List<String> actions;
|
||||
}
|
||||
|
||||
public boolean isGranted(Authentication auth) {
|
||||
return createFilter().test(auth);
|
||||
}
|
||||
|
||||
public Predicate<Authentication> createFilter() {
|
||||
RoleSpec role = this.role;
|
||||
List<PermissionSpec> permissions = this.permissions;
|
||||
List<Predicate<Authentication>> all = new ArrayList<>();
|
||||
|
||||
if (null != role && role.getIdList() != null) {
|
||||
all.add(auth -> auth.hasDimension(DefaultDimensionType.role.getId(), role.getIdList()));
|
||||
}
|
||||
|
||||
if (null != permissions) {
|
||||
for (PermissionSpec permission : permissions) {
|
||||
all.add(auth -> auth.hasPermission(permission.getId(), permission.getActions()));
|
||||
}
|
||||
}
|
||||
|
||||
Predicate<Authentication> temp = null;
|
||||
for (Predicate<Authentication> predicate : all) {
|
||||
if (temp == null) {
|
||||
temp = predicate;
|
||||
} else {
|
||||
temp = temp.and(predicate);
|
||||
}
|
||||
}
|
||||
return temp == null ? auth -> true : temp;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,448 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.authorize;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hswebframework.web.authorization.*;
|
||||
import org.hswebframework.web.authorization.simple.*;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.core.metadata.Jsonable;
|
||||
import org.jetlinks.core.utils.RecyclerUtils;
|
||||
import org.jetlinks.core.utils.SerializeUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Setter
|
||||
public class FastSerializableAuthentication extends SimpleAuthentication
|
||||
implements Externalizable, Jsonable {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FastSerializableAuthentication.class);
|
||||
|
||||
static {
|
||||
SerializeUtils.registerSerializer(
|
||||
0x90,
|
||||
FastSerializableAuthentication.class,
|
||||
(ignore) -> new FastSerializableAuthentication());
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Authentication of(Object jsonOrObject, boolean share) {
|
||||
if (jsonOrObject == null) {
|
||||
return null;
|
||||
}
|
||||
//json
|
||||
if (jsonOrObject instanceof String) {
|
||||
return of((String) jsonOrObject, share);
|
||||
}
|
||||
// map
|
||||
if (jsonOrObject instanceof Map) {
|
||||
FastSerializableAuthentication fast = new FastSerializableAuthentication();
|
||||
fast.shared = share;
|
||||
fast.fromJson(new JSONObject((Map) jsonOrObject));
|
||||
return fast;
|
||||
}
|
||||
//auth
|
||||
if (jsonOrObject instanceof Authentication) {
|
||||
return of(((Authentication) jsonOrObject));
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static Authentication of(String json, boolean share) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
FastSerializableAuthentication fast = new FastSerializableAuthentication();
|
||||
fast.shared = share;
|
||||
fast.fromJson(JSON.parseObject(json));
|
||||
return fast;
|
||||
}
|
||||
|
||||
|
||||
public static Authentication of(Authentication auth) {
|
||||
return of(auth, false);
|
||||
}
|
||||
|
||||
public static Authentication of(Authentication auth, boolean simplify) {
|
||||
if (auth instanceof FastSerializableAuthentication) {
|
||||
((FastSerializableAuthentication) auth).simplify = simplify;
|
||||
return auth;
|
||||
}
|
||||
FastSerializableAuthentication fast = new FastSerializableAuthentication();
|
||||
fast.setUser(auth.getUser());
|
||||
fast.setSimplify(simplify);
|
||||
fast.getPermissions().addAll(auth.getPermissions());
|
||||
fast.getDimensions().addAll(auth.getDimensions());
|
||||
fast.getAttributes().putAll(auth.getAttributes());
|
||||
return fast;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FastSerializableAuthentication newInstance() {
|
||||
return new FastSerializableAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否简化,为true时,不序列化权限名称
|
||||
*
|
||||
* @see Permission#getName()
|
||||
*/
|
||||
private boolean simplify = false;
|
||||
|
||||
private transient boolean shared;
|
||||
|
||||
public void makeShared() {
|
||||
shared = true;
|
||||
List<Dimension> dimensions = getDimensions()
|
||||
.stream()
|
||||
.map(RecyclerUtils::intern)
|
||||
.collect(Collectors.toList());
|
||||
setDimensions(dimensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
String userId = null;
|
||||
try {
|
||||
out.writeByte(0x01);
|
||||
//是否简化模式
|
||||
out.writeBoolean(simplify);
|
||||
|
||||
//user
|
||||
User user = getUser();
|
||||
out.writeUTF(user.getId() == null ? "" : (userId = user.getId()));
|
||||
SerializeUtils.writeNullableUTF(user.getName(), out);
|
||||
out.writeUTF(user.getUsername() == null ? "" : user.getUsername());
|
||||
SerializeUtils.writeNullableUTF(user.getUserType(), out);
|
||||
SerializeUtils.writeKeyValue(user.getOptions(), out);
|
||||
|
||||
//permission
|
||||
{
|
||||
List<Permission> permissions = getPermissions();
|
||||
if (permissions == null) {
|
||||
permissions = Collections.emptyList();
|
||||
}
|
||||
out.writeInt(permissions.size());
|
||||
for (Permission permission : permissions) {
|
||||
write(permission, out);
|
||||
}
|
||||
}
|
||||
//dimension
|
||||
{
|
||||
List<Dimension> dimensions = getDimensions();
|
||||
if (dimensions == null) {
|
||||
dimensions = Collections.emptyList();
|
||||
}
|
||||
out.writeInt(dimensions.size());
|
||||
for (Dimension permission : dimensions) {
|
||||
write(permission, out);
|
||||
}
|
||||
}
|
||||
|
||||
SerializeUtils.writeKeyValue(getAttributes(), out);
|
||||
|
||||
} catch (Throwable e) {
|
||||
log.warn("write FastSerializableAuthentication [{}] error", userId, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
byte version = in.readByte();
|
||||
simplify = in.readBoolean();
|
||||
|
||||
//user
|
||||
SimpleUser user = new SimpleUser();
|
||||
user.setId(in.readUTF());
|
||||
user.setName(SerializeUtils.readNullableUTF(in));
|
||||
user.setUsername(in.readUTF());
|
||||
user.setUserType(SerializeUtils.readNullableUTF(in));
|
||||
user.setOptions(SerializeUtils.readMap(in, Maps::newHashMapWithExpectedSize));
|
||||
|
||||
setUser0(user);
|
||||
|
||||
//permission
|
||||
{
|
||||
int size = in.readInt();
|
||||
List<Permission> permissions = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Permission permission = readPermission(in);
|
||||
permissions.add(permission);
|
||||
}
|
||||
setPermissions(permissions);
|
||||
}
|
||||
//dimension
|
||||
{
|
||||
int size = in.readInt();
|
||||
Set<Dimension> dimensions = new HashSet<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Dimension dimension = readDimension(in);
|
||||
dimensions.add(dimension);
|
||||
}
|
||||
setDimensions(dimensions);
|
||||
}
|
||||
|
||||
setAttributes(SerializeUtils.readMap(in, Maps::newHashMapWithExpectedSize));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Dimension readDimension(ObjectInput in) {
|
||||
SimpleDimension dimension = new SimpleDimension();
|
||||
dimension.setId(in.readUTF());
|
||||
dimension.setName(in.readUTF());
|
||||
|
||||
dimension.setOptions(SerializeUtils.readMap(
|
||||
in,
|
||||
k -> RecyclerUtils.intern(String.valueOf(k)),
|
||||
Function.identity(),
|
||||
Maps::newHashMapWithExpectedSize));
|
||||
|
||||
boolean known = in.readBoolean();
|
||||
if (known) {
|
||||
KnownDimension knownDimension = KnownDimension.ALL[in.readByte()];
|
||||
dimension.setType(knownDimension.type);
|
||||
} else {
|
||||
SimpleDimensionType type = new SimpleDimensionType();
|
||||
type.setId(in.readUTF());
|
||||
type.setName(in.readUTF());
|
||||
dimension.setType(RecyclerUtils.intern(type));
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
private void write(Dimension dimension, ObjectOutput out) {
|
||||
out.writeUTF(dimension.getId());
|
||||
out.writeUTF(dimension.getName() == null ? "" : dimension.getName());
|
||||
|
||||
SerializeUtils.writeKeyValue(dimension.getOptions(), out);
|
||||
|
||||
KnownDimension knownDimension = KnownDimension.MAPPING.get(dimension.getType().getId());
|
||||
|
||||
out.writeBoolean(knownDimension != null);
|
||||
if (knownDimension != null) {
|
||||
out.writeByte(knownDimension.ordinal());
|
||||
} else {
|
||||
out.writeUTF(dimension.getType().getId());
|
||||
out.writeUTF(dimension.getType().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
private Permission readPermission(ObjectInput in) {
|
||||
SimplePermission permission = new SimplePermission();
|
||||
permission.setId(in.readUTF());
|
||||
if (!simplify) {
|
||||
permission.setName(in.readUTF());
|
||||
} else {
|
||||
permission.setName(permission.getId());
|
||||
}
|
||||
permission.setOptions(SerializeUtils.readMap(in, Maps::newHashMapWithExpectedSize));
|
||||
|
||||
int actionSize = in.readUnsignedShort();
|
||||
Set<String> actions = Sets.newHashSetWithExpectedSize(actionSize);
|
||||
for (int i = 0; i < actionSize; i++) {
|
||||
if (in.readBoolean()) {
|
||||
actions.add(KnownAction.ALL[in.readByte()].action);
|
||||
} else {
|
||||
actions.add(in.readUTF());
|
||||
}
|
||||
}
|
||||
permission.setActions(actions);
|
||||
return permission;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void write(Permission permission, ObjectOutput out) {
|
||||
out.writeUTF(permission.getId());
|
||||
if (!simplify) {
|
||||
out.writeUTF(permission.getName() == null ? "" : permission.getName());
|
||||
}
|
||||
|
||||
SerializeUtils.writeKeyValue(permission.getOptions(), out);
|
||||
Set<String> actions = permission.getActions();
|
||||
out.writeShort(actions.size());
|
||||
|
||||
for (String action : actions) {
|
||||
KnownAction knownAction = KnownAction.ACTION_MAP.get(action);
|
||||
out.writeBoolean(knownAction != null);
|
||||
if (null != knownAction) {
|
||||
out.writeByte(knownAction.ordinal());
|
||||
} else {
|
||||
out.writeUTF(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
enum KnownDimension {
|
||||
user(DefaultDimensionType.user),
|
||||
role(DefaultDimensionType.role),
|
||||
org(OrgDimensionType.org),
|
||||
parentOrg(OrgDimensionType.parentOrg);
|
||||
private final DimensionType type;
|
||||
static final KnownDimension[] ALL = values();
|
||||
static final Map<Object, KnownDimension> MAPPING = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (KnownDimension value : ALL) {
|
||||
MAPPING.put(value, value);
|
||||
MAPPING.put(value.ordinal(), value);
|
||||
MAPPING.put(value.name(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum KnownAction {
|
||||
query,
|
||||
get,
|
||||
update,
|
||||
save,
|
||||
delete,
|
||||
export,
|
||||
_import(Permission.ACTION_IMPORT),
|
||||
enable,
|
||||
disable;
|
||||
static final KnownAction[] ALL = values();
|
||||
static final Map<Object, KnownAction> ACTION_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (KnownAction value : ALL) {
|
||||
ACTION_MAP.put(value, value);
|
||||
ACTION_MAP.put(value.ordinal(), value);
|
||||
ACTION_MAP.put(value.action, value);
|
||||
}
|
||||
}
|
||||
|
||||
private final String action;
|
||||
|
||||
KnownAction() {
|
||||
this.action = name();
|
||||
}
|
||||
|
||||
KnownAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJson() {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("user", SerializeUtils.convertToSafelySerializable(getUser()));
|
||||
obj.put("permissions", SerializeUtils.convertToSafelySerializable(getPermissions()));
|
||||
//忽略user
|
||||
obj.put("dimensions", SerializeUtils.convertToSafelySerializable(
|
||||
Collections2.filter(getDimensions(), i -> !(i instanceof User))
|
||||
));
|
||||
obj.put("attributes", new HashMap<>(getAttributes()));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromJson(JSONObject json) {
|
||||
JSONObject user = json.getJSONObject("user");
|
||||
if (user != null) {
|
||||
setUser(user.toJavaObject(SimpleUser.class));
|
||||
}
|
||||
JSONArray permissions = json.getJSONArray("permissions");
|
||||
if (permissions != null) {
|
||||
for (int i = 0, size = permissions.size(); i < size; i++) {
|
||||
JSONObject permission = permissions.getJSONObject(i);
|
||||
//不再支持
|
||||
permission.remove("dataAccesses");
|
||||
|
||||
Object actions = permission.remove("actions");
|
||||
SimplePermission perm = permission.toJavaObject(SimplePermission.class);
|
||||
|
||||
if (actions instanceof Collection) {
|
||||
@SuppressWarnings("all")
|
||||
Collection<Object> _actions = (Collection<Object>) actions;
|
||||
Set<String> acts = Sets.newHashSetWithExpectedSize(_actions.size());
|
||||
for (Object action : _actions) {
|
||||
KnownAction act = KnownAction.ACTION_MAP.get(action);
|
||||
if (act == null) {
|
||||
acts.add(String.valueOf(action));
|
||||
} else {
|
||||
acts.add(act.action);
|
||||
}
|
||||
}
|
||||
perm.setActions(acts);
|
||||
}
|
||||
|
||||
getPermissions().add(shared ? RecyclerUtils.intern(perm) : perm);
|
||||
}
|
||||
}
|
||||
JSONArray dimensions = json.getJSONArray("dimensions");
|
||||
|
||||
if (dimensions != null) {
|
||||
for (int i = 0, size = dimensions.size(); i < size; i++) {
|
||||
JSONObject dimension = dimensions.getJSONObject(i);
|
||||
Object type = dimension.remove("type");
|
||||
if (type == null) {
|
||||
continue;
|
||||
}
|
||||
SimpleDimension simpleDimension = dimension.toJavaObject(SimpleDimension.class);
|
||||
if (type instanceof DimensionType) {
|
||||
simpleDimension.setType((DimensionType) type);
|
||||
} else {
|
||||
KnownDimension knownDimension = KnownDimension.MAPPING.get(type);
|
||||
if (knownDimension != null) {
|
||||
simpleDimension.setType(knownDimension.type);
|
||||
} else {
|
||||
SimpleDimensionType dimensionType;
|
||||
if (type instanceof String) {
|
||||
dimensionType = SimpleDimensionType.of(String.valueOf(type));
|
||||
} else {
|
||||
dimensionType = FastBeanCopier.copy(type, new SimpleDimensionType());
|
||||
}
|
||||
if (StringUtils.isNoneEmpty(dimensionType.getId())) {
|
||||
simpleDimension.setType(shared ? RecyclerUtils.intern(dimensionType) : dimensionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
getDimensions().add(shared ? RecyclerUtils.intern(simpleDimension) : simpleDimension);
|
||||
}
|
||||
}
|
||||
JSONObject attr = json.getJSONObject("attributes");
|
||||
if (attr != null) {
|
||||
getAttributes().putAll(Maps.transformValues(attr, Serializable.class::cast));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.authorize;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Generated;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.authorization.DimensionType;
|
||||
|
||||
/**
|
||||
* @author wangzheng
|
||||
* @since 1.0
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Generated
|
||||
public enum OrgDimensionType implements DimensionType {
|
||||
org("org","组织"),
|
||||
parentOrg("parentOrg","上级组织");
|
||||
|
||||
private final String id;
|
||||
private final String name;
|
||||
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
import org.jetlinks.community.Operation;
|
||||
import org.jetlinks.community.OperationSource;
|
||||
import org.jetlinks.community.OperationType;
|
||||
import org.jetlinks.community.event.SystemEventHolder;
|
||||
import org.jetlinks.community.utils.TimeUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
||||
|
||||
public abstract class AbstractBufferEviction implements BufferEviction {
|
||||
|
||||
public static final OperationType OPERATION_TYPE = OperationType.of("buffer-eviction", "缓冲区数据丢弃");
|
||||
|
||||
private static final AtomicLongFieldUpdater<AbstractBufferEviction>
|
||||
LAST_EVENT_TIME = AtomicLongFieldUpdater.newUpdater(AbstractBufferEviction.class, "lastEventTime");
|
||||
private static final AtomicIntegerFieldUpdater<AbstractBufferEviction>
|
||||
LAST_TIMES = AtomicIntegerFieldUpdater.newUpdater(AbstractBufferEviction.class, "lastTimes");
|
||||
|
||||
//最大事件推送频率
|
||||
//可通过java -Djetlinks.buffer.eviction.event.max-interval=10m修改配置
|
||||
private static final long MAX_EVENT_INTERVAL =
|
||||
TimeUtils.parse(System.getProperty("jetlinks.buffer.eviction.event.max-interval", "10m")).toMillis();
|
||||
|
||||
private volatile long lastEventTime;
|
||||
private volatile int lastTimes;
|
||||
|
||||
abstract boolean doEviction(EvictionContext context);
|
||||
|
||||
@Override
|
||||
public boolean tryEviction(EvictionContext context) {
|
||||
if (doEviction(context)) {
|
||||
sendEvent(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String operationCode() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
private void sendEvent(EvictionContext context) {
|
||||
long now = System.currentTimeMillis();
|
||||
long time = LAST_EVENT_TIME.get(this);
|
||||
//记录事件推送周期内总共触发了多少次
|
||||
LAST_TIMES.incrementAndGet(this);
|
||||
|
||||
//超过间隔事件则推送事件,防止推送太多错误事件
|
||||
if (now - time > MAX_EVENT_INTERVAL) {
|
||||
LAST_EVENT_TIME.set(this, now);
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
|
||||
//缓冲区数量
|
||||
info.put("bufferSize", context.size(EvictionContext.BufferType.buffer));
|
||||
//死数据数量
|
||||
info.put("deadSize", context.size(EvictionContext.BufferType.dead));
|
||||
//总计触发次数
|
||||
info.put("times", LAST_TIMES.getAndSet(this, 0));
|
||||
|
||||
//应用自定义的数据,比如磁盘剩余空间等信息
|
||||
applyEventData(info);
|
||||
|
||||
//推送系统事件
|
||||
SystemEventHolder.warn(
|
||||
Operation.of(
|
||||
OperationSource.of(context.getName(), "eviction"),
|
||||
OPERATION_TYPE),
|
||||
operationCode(),
|
||||
info
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyEventData(Map<String, Object> data) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 缓存淘汰策略
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface BufferEviction {
|
||||
|
||||
BufferEviction NONE = new BufferEviction() {
|
||||
@Override
|
||||
public boolean tryEviction(EvictionContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "None";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据磁盘使用率来进行淘汰,磁盘使用率超过阈值时则淘汰旧数据
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param threshold 使用率阈值 范围为0-1 .如: 0.8 表示磁盘使用率超过80%则丢弃数据
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
static BufferEviction disk(String path, float threshold) {
|
||||
return new DiskUsageEviction(new File(path), threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据磁盘可用空间来进行淘汰,磁盘剩余空间低于阈值时则淘汰旧数据
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param minUsableDataSize 磁盘最小可用空间阈值,当磁盘可用空间低于此值时则则淘汰旧数据
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
static BufferEviction disk(String path, DataSize minUsableDataSize) {
|
||||
return new DiskFreeEviction(new File(path), minUsableDataSize.toBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缓冲区数量来淘汰数据,当数量超过指定阈值后则淘汰旧数据
|
||||
*
|
||||
* @param bufferLimit 数量阈值
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
static BufferEviction limit(long bufferLimit) {
|
||||
return limit(bufferLimit, bufferLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缓冲区数量来淘汰数据,当数量超过指定阈值后则淘汰旧数据
|
||||
*
|
||||
* @param bufferLimit 缓冲数量阈值
|
||||
* @param deadLimit 死数据数量阈值
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
static BufferEviction limit(long bufferLimit, long deadLimit) {
|
||||
return new SizeLimitEviction(bufferLimit, deadLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缓冲区数量来淘汰死数据
|
||||
*
|
||||
* @param deadLimit 死数据数量阈值
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
static BufferEviction deadLimit(long deadLimit) {
|
||||
return new SizeLimitEviction(-1, deadLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试执行淘汰
|
||||
*
|
||||
* @param context 上下文
|
||||
* @return 是否有数据被淘汰
|
||||
*/
|
||||
boolean tryEviction(EvictionContext context);
|
||||
|
||||
/**
|
||||
* 组合另外一个淘汰策略,2个策略同时执行.
|
||||
*
|
||||
* @param after 后续策略
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
default BufferEviction and(BufferEviction after) {
|
||||
BufferEviction self = this;
|
||||
return new BufferEviction() {
|
||||
@Override
|
||||
public boolean tryEviction(EvictionContext context) {
|
||||
return self.tryEviction(context) & after.tryEviction(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return self + " and " + after;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合另外一个淘汰策略,当前策略淘汰了数据才执行另外一个策略
|
||||
*
|
||||
* @param after 后续策略
|
||||
* @return 淘汰策略
|
||||
*/
|
||||
default BufferEviction then(BufferEviction after) {
|
||||
BufferEviction self = this;
|
||||
return new BufferEviction() {
|
||||
@Override
|
||||
public boolean tryEviction(EvictionContext context) {
|
||||
if (self.tryEviction(context)) {
|
||||
after.tryEviction(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return self + " then " + after;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class BufferEvictionSpec {
|
||||
|
||||
public static final BufferEviction DEFAULT = new BufferEvictionSpec().build();
|
||||
|
||||
//最大队列数量,超过则淘汰最旧的数据
|
||||
private int maxSize = -1;
|
||||
|
||||
//最大死信数量,超过则淘汰dead数据
|
||||
private int maxDeadSize = Integer.getInteger("jetlinks.buffer.dead.limit", 100_0000);
|
||||
|
||||
//根据磁盘空间淘汰数据
|
||||
private DataSize diskFree = DataSize.parse(System.getProperty("jetlinks.buffer.disk.free.threshold", "4GB"));
|
||||
|
||||
//磁盘最大使用率
|
||||
private float diskThreshold;
|
||||
|
||||
//判断磁盘空间大小的目录
|
||||
private String diskPath = System.getProperty("jetlinks.buffer.disk.free.path", "./");
|
||||
|
||||
public BufferEviction build() {
|
||||
|
||||
BufferEviction
|
||||
eviction = null,
|
||||
size = BufferEviction.limit(maxSize, maxDeadSize),
|
||||
disk = null;
|
||||
|
||||
if (diskThreshold > 0) {
|
||||
disk = BufferEviction.disk(diskPath, diskThreshold);
|
||||
} else if (diskFree != null) {
|
||||
disk = BufferEviction.disk(diskPath, diskFree);
|
||||
}
|
||||
|
||||
if (disk != null) {
|
||||
eviction = disk;
|
||||
}
|
||||
|
||||
if (eviction == null) {
|
||||
eviction = size;
|
||||
} else {
|
||||
eviction = eviction.then(size);
|
||||
}
|
||||
|
||||
return eviction;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class BufferProperties {
|
||||
//缓冲文件存储目录
|
||||
private String filePath;
|
||||
|
||||
//缓冲区大小,超过此大小将执行 handler 处理逻辑
|
||||
private int size = 1000;
|
||||
|
||||
//缓冲超时时间
|
||||
private Duration timeout = Duration.ofSeconds(1);
|
||||
|
||||
//并行度,表示支持并行写入的最大线程数.
|
||||
private int parallelism = Math.max(1, Runtime.getRuntime().availableProcessors());
|
||||
|
||||
//最大重试次数,超过此次数的数据将会放入死队列.
|
||||
private long maxRetryTimes = 64;
|
||||
|
||||
//文件操作的最大并行度,默认为1,不建议设置超过4.
|
||||
private int fileConcurrency = 1;
|
||||
|
||||
//消费策略 默认先进先出
|
||||
private ConsumeStrategy strategy = ConsumeStrategy.FIFO;
|
||||
|
||||
//淘汰策略
|
||||
private BufferEvictionSpec eviction = new BufferEvictionSpec();
|
||||
|
||||
public boolean isExceededRetryCount(int count) {
|
||||
return maxRetryTimes > 0 && count >= maxRetryTimes;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.jetlinks.community.utils.ErrorUtils;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.QueryTimeoutException;
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class BufferSettings {
|
||||
|
||||
private static final Predicate<Throwable> DEFAULT_RETRY_WHEN_ERROR =
|
||||
e -> ErrorUtils.hasException(e, IOException.class,
|
||||
IllegalStateException.class,
|
||||
RejectedExecutionException.class,
|
||||
TimeoutException.class,
|
||||
DataAccessResourceFailureException.class,
|
||||
CannotCreateTransactionException.class,
|
||||
QueryTimeoutException.class);
|
||||
|
||||
public static Predicate<Throwable> defaultRetryWhenError() {
|
||||
return DEFAULT_RETRY_WHEN_ERROR;
|
||||
}
|
||||
|
||||
public static BufferEviction defaultEviction(){
|
||||
return BufferEvictionSpec.DEFAULT;
|
||||
}
|
||||
|
||||
private final String filePath;
|
||||
|
||||
private final String fileName;
|
||||
|
||||
//缓存淘汰策略
|
||||
private final BufferEviction eviction;
|
||||
|
||||
private final Predicate<Throwable> retryWhenError;
|
||||
|
||||
//缓冲区大小,超过此大小将执行 handler 处理逻辑
|
||||
private final int bufferSize;
|
||||
|
||||
//缓冲超时时间
|
||||
private final Duration bufferTimeout;
|
||||
|
||||
//并行度,表示支持并行写入的最大线程数.
|
||||
private final int parallelism;
|
||||
|
||||
//最大重试次数,超过此次数的数据将会放入死队列.
|
||||
private final long maxRetryTimes;
|
||||
|
||||
private final int fileConcurrency;
|
||||
|
||||
private final ConsumeStrategy strategy;
|
||||
|
||||
public static BufferSettings create(String filePath, String fileName) {
|
||||
return new BufferSettings(
|
||||
filePath,
|
||||
fileName,
|
||||
defaultEviction(),
|
||||
//默认重试逻辑
|
||||
defaultRetryWhenError(),
|
||||
1000,
|
||||
Duration.ofSeconds(1),
|
||||
Math.max(1, Runtime.getRuntime().availableProcessors() / 2),
|
||||
5,
|
||||
1,
|
||||
ConsumeStrategy.FIFO);
|
||||
}
|
||||
|
||||
public static BufferSettings create(BufferProperties properties) {
|
||||
return create("buffer.queue", properties);
|
||||
}
|
||||
|
||||
public static BufferSettings create(String fileName, BufferProperties properties) {
|
||||
return create(properties.getFilePath(), fileName).properties(properties);
|
||||
}
|
||||
|
||||
public BufferSettings eviction(BufferEviction eviction) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings bufferSize(int bufferSize) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings bufferTimeout(Duration bufferTimeout) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings parallelism(int parallelism) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings maxRetry(int maxRetryTimes) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings retryWhenError(Predicate<Throwable> retryWhenError) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
Objects.requireNonNull(retryWhenError),
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings fileConcurrency(int fileConcurrency) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings strategy(ConsumeStrategy strategy) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
eviction,
|
||||
retryWhenError,
|
||||
bufferSize,
|
||||
bufferTimeout,
|
||||
parallelism,
|
||||
maxRetryTimes,
|
||||
fileConcurrency,
|
||||
strategy);
|
||||
}
|
||||
|
||||
public BufferSettings properties(BufferProperties properties) {
|
||||
return new BufferSettings(filePath,
|
||||
fileName,
|
||||
properties.getEviction().build(),
|
||||
Objects.requireNonNull(retryWhenError),
|
||||
properties.getSize(),
|
||||
properties.getTimeout(),
|
||||
properties.getParallelism(),
|
||||
properties.getMaxRetryTimes(),
|
||||
properties.getFileConcurrency(),
|
||||
properties.getStrategy()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
/**
|
||||
* 已缓冲的数据
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author zhouhao
|
||||
* @since 2.2
|
||||
*/
|
||||
public interface Buffered<T> {
|
||||
|
||||
/**
|
||||
* @return 数据
|
||||
*/
|
||||
T getData();
|
||||
|
||||
/**
|
||||
* @return 当前重试次数
|
||||
*/
|
||||
int getRetryTimes();
|
||||
|
||||
/**
|
||||
* 标记是否重试此数据
|
||||
*/
|
||||
void retry(boolean retry);
|
||||
|
||||
/**
|
||||
* 标记此数据为死信
|
||||
*/
|
||||
void dead();
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
public enum ConsumeStrategy {
|
||||
|
||||
// 先进先出
|
||||
FIFO,
|
||||
|
||||
// 后进先出
|
||||
LIFO
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
|
||||
import org.jetlinks.community.utils.FormatUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
class DiskFreeEviction extends AbstractBufferEviction {
|
||||
|
||||
private final File path;
|
||||
private final long minUsableBytes;
|
||||
|
||||
public DiskFreeEviction(File path, long minUsableBytes) {
|
||||
this.path = path;
|
||||
this.minUsableBytes = minUsableBytes;
|
||||
}
|
||||
|
||||
private volatile long usableSpace = -1;
|
||||
private volatile long lastUpdateTime;
|
||||
|
||||
@Override
|
||||
public boolean doEviction(EvictionContext context) {
|
||||
tryUpdate();
|
||||
|
||||
if (freeOutOfThreshold()) {
|
||||
context.removeOldest(EvictionContext.BufferType.buffer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean freeOutOfThreshold() {
|
||||
return usableSpace != -1 && usableSpace <= minUsableBytes;
|
||||
}
|
||||
|
||||
private void tryUpdate() {
|
||||
long now = System.currentTimeMillis();
|
||||
//1秒更新一次
|
||||
if (now - lastUpdateTime <= 1000) {
|
||||
return;
|
||||
}
|
||||
usableSpace = path.getUsableSpace();
|
||||
lastUpdateTime = now;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEventData(Map<String, Object> data) {
|
||||
data.put("usableSpace", DataSize.ofBytes(usableSpace).toMegabytes());
|
||||
data.put("minUsableBytes", DataSize.ofBytes(minUsableBytes).toMegabytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DiskFree(path=" + path
|
||||
+ ",space=" + FormatUtils.formatDataSize(usableSpace) + "/" + FormatUtils.formatDataSize(minUsableBytes) + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
class DiskUsageEviction extends AbstractBufferEviction {
|
||||
|
||||
private final File path;
|
||||
private final float threshold;
|
||||
|
||||
public DiskUsageEviction(File path, float threshold) {
|
||||
this.path = path;
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
private volatile float usage;
|
||||
private volatile long lastUpdateTime;
|
||||
|
||||
@Override
|
||||
public boolean doEviction(EvictionContext context) {
|
||||
tryUpdate();
|
||||
|
||||
if (freeOutOfThreshold()) {
|
||||
context.removeOldest(EvictionContext.BufferType.buffer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean freeOutOfThreshold() {
|
||||
return usage >= threshold;
|
||||
}
|
||||
|
||||
private void tryUpdate() {
|
||||
long now = System.currentTimeMillis();
|
||||
//1秒更新一次
|
||||
if (now - lastUpdateTime <= 1000) {
|
||||
return;
|
||||
}
|
||||
long total = path.getTotalSpace();
|
||||
long usable = path.getUsableSpace();
|
||||
|
||||
usage = (float) ((total - usable) / (double) total);
|
||||
lastUpdateTime = now;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEventData(Map<String, Object> data) {
|
||||
data.put("usage", String.format("%.2f%%", usage * 100));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DiskUsage(path=" + path
|
||||
+ ", threshold=" + String.format("%.2f%%", threshold * 100)
|
||||
+ ", usage=" + String.format("%.2f%%", usage * 100) + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
/**
|
||||
* 缓冲淘汰上下文
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface EvictionContext {
|
||||
|
||||
/**
|
||||
* 获取指定类型的数据量
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 数据量
|
||||
*/
|
||||
long size(BufferType type);
|
||||
|
||||
/**
|
||||
* 删除最新的数据
|
||||
*
|
||||
* @param type 类型
|
||||
*/
|
||||
void removeLatest(BufferType type);
|
||||
|
||||
/**
|
||||
* 删除最旧的数据
|
||||
*
|
||||
* @param type 类型
|
||||
*/
|
||||
void removeOldest(BufferType type);
|
||||
|
||||
/**
|
||||
* @return 缓冲区名称, 用于区分多个不同的缓冲区
|
||||
*/
|
||||
String getName();
|
||||
|
||||
enum BufferType {
|
||||
//缓冲区
|
||||
buffer,
|
||||
//死数据
|
||||
dead
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
public interface MemoryUsage {
|
||||
|
||||
int usage();
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.buffer;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@AllArgsConstructor
|
||||
class SizeLimitEviction extends AbstractBufferEviction {
|
||||
|
||||
private final long bufferLimit;
|
||||
private final long deadLimit;
|
||||
|
||||
@Override
|
||||
public boolean doEviction(EvictionContext context) {
|
||||
boolean anyEviction = false;
|
||||
if (bufferLimit > 0 && context.size(EvictionContext.BufferType.buffer) >= bufferLimit) {
|
||||
context.removeOldest(EvictionContext.BufferType.buffer);
|
||||
anyEviction = true;
|
||||
}
|
||||
if (deadLimit > 0 && context.size(EvictionContext.BufferType.dead) >= deadLimit) {
|
||||
context.removeOldest(EvictionContext.BufferType.dead);
|
||||
anyEviction = true;
|
||||
}
|
||||
return anyEviction;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEventData(Map<String, Object> data) {
|
||||
data.put("bufferLimit", bufferLimit);
|
||||
data.put("deadLimit", deadLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SizeLimit(buffer=" + bufferLimit + ", dead=" + deadLimit + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.codec;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface ObjectSerializer {
|
||||
|
||||
ObjectInput createInput(InputStream stream);
|
||||
|
||||
ObjectOutput createOutput(OutputStream stream);
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
import lombok.SneakyThrows;
|
||||
import org.jetlinks.core.utils.SerializeUtils;
|
||||
import org.nustaq.serialization.FSTConfiguration;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Base64;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Serializers {
|
||||
|
||||
private static final ObjectSerializer JDK = new ObjectSerializer() {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public ObjectInput createInput(InputStream stream) {
|
||||
return new ObjectInputStream(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public ObjectOutput createOutput(OutputStream stream) {
|
||||
return new ObjectOutputStream(stream);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ObjectSerializer FST = new ObjectSerializer() {
|
||||
|
||||
final FastThreadLocal<FSTConfiguration> conf =
|
||||
new FastThreadLocal<FSTConfiguration>() {
|
||||
@Override
|
||||
protected FSTConfiguration initialValue() {
|
||||
FSTConfiguration configuration = FSTConfiguration.createDefaultConfiguration();
|
||||
configuration.setForceSerializable(true);
|
||||
configuration.setClassLoader(FST.getClass().getClassLoader());
|
||||
return configuration;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public ObjectInput createInput(InputStream stream) {
|
||||
return conf.get().getObjectInput(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public ObjectOutput createOutput(OutputStream stream) {
|
||||
return conf.get().getObjectOutput(stream);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ObjectSerializer DEFAULT;
|
||||
|
||||
static {
|
||||
DEFAULT = System.getProperty("jetlinks.object.serializer.type", "fst").equals("fst") ? FST : JDK;
|
||||
}
|
||||
|
||||
public static ObjectSerializer jdk() {
|
||||
return JDK;
|
||||
}
|
||||
|
||||
public static ObjectSerializer fst() {
|
||||
return FST;
|
||||
}
|
||||
|
||||
|
||||
public static ObjectSerializer getDefault() {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
public static String serializeToBase64(Object source) {
|
||||
return Base64.getEncoder().encodeToString(serialize(source));
|
||||
}
|
||||
|
||||
public static Object deserializeFromBase64(String base64) {
|
||||
return deserialize(Base64.getDecoder().decode(base64));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private static final FastThreadLocal<ByteArrayOutputStream>
|
||||
SHARED_STREAM = new FastThreadLocal<ByteArrayOutputStream>() {
|
||||
@Override
|
||||
protected ByteArrayOutputStream initialValue() {
|
||||
return new ByteArrayOutputStream();
|
||||
}
|
||||
};
|
||||
|
||||
@SneakyThrows
|
||||
public static ByteBuf serializeExternal(Externalizable source) {
|
||||
ByteArrayOutputStream outputStream = SHARED_STREAM.get();
|
||||
if (outputStream.size() != 0) {
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
}
|
||||
try (ObjectOutput output = getDefault().createOutput(outputStream)) {
|
||||
source.writeExternal(output);
|
||||
output.flush();
|
||||
return Unpooled.wrappedBuffer(outputStream.toByteArray());
|
||||
} finally {
|
||||
outputStream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Externalizable> T deserializeExternal(ByteBuf buffer, Supplier<T> instance) {
|
||||
try (ObjectInput input = getDefault().createInput(new ByteBufInputStream(buffer, true))) {
|
||||
T data = instance.get();
|
||||
data.readExternal(input);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public static byte[] serialize(Object source) {
|
||||
ByteArrayOutputStream outputStream = SHARED_STREAM.get();
|
||||
if (outputStream.size() != 0) {
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
}
|
||||
try (ObjectOutput output = getDefault().createOutput(outputStream)) {
|
||||
SerializeUtils.writeObject(source, output);
|
||||
output.flush();
|
||||
return outputStream.toByteArray();
|
||||
} finally {
|
||||
outputStream.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Object deserialize(byte[] data) {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(data);
|
||||
try (ObjectInput input = getDefault().createInput(stream)) {
|
||||
return SerializeUtils.readObject(input);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.core.command.CommandSupport;
|
||||
import org.jetlinks.core.utils.SerializeUtils;
|
||||
import org.jetlinks.community.spi.Provider;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 命令支持提供者,用于针对多个基于命令模式的可选模块依赖时的解耦.
|
||||
* <p>
|
||||
*
|
||||
* @author zhouhao
|
||||
* @see org.jetlinks.sdk.server.SdkServices
|
||||
* @see InternalSdkServices
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface CommandSupportManagerProvider {
|
||||
/**
|
||||
* 所有支持的提供商
|
||||
*/
|
||||
Provider<CommandSupportManagerProvider> supports = Provider.create(CommandSupportManagerProvider.class);
|
||||
|
||||
/**
|
||||
* 命令服务提供商标识
|
||||
*
|
||||
* @return 唯一标识
|
||||
*/
|
||||
String getProvider();
|
||||
|
||||
/**
|
||||
* 获取命令支持,不同的命令管理支持多种命令支持,可能通过id进行区分,具体规则由对应服务实 现
|
||||
*
|
||||
* @param id 命令ID标识
|
||||
* @param options 拓展配置
|
||||
* @return CommandSupport
|
||||
* @see CommandSupportManagerProviders#getCommandSupport(String, Map)
|
||||
*/
|
||||
Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options);
|
||||
|
||||
/**
|
||||
* 获取所有支持的信息
|
||||
*
|
||||
* @return id
|
||||
*/
|
||||
default Flux<CommandSupportInfo> getSupportInfo() {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
@Setter
|
||||
class CommandSupportInfo implements Externalizable {
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
public CommandSupportInfo copy() {
|
||||
return FastBeanCopier.copy(this, new CommandSupportInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serviceId serviceId
|
||||
* @return this
|
||||
* @see CommandSupportManagerProviders#getCommandSupport(String)
|
||||
*/
|
||||
public CommandSupportInfo appendService(String serviceId) {
|
||||
if (this.id == null) {
|
||||
this.id = serviceId;
|
||||
} else {
|
||||
this.id = serviceId + ":" + id;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
SerializeUtils.writeNullableUTF(id, out);
|
||||
SerializeUtils.writeNullableUTF(name, out);
|
||||
SerializeUtils.writeNullableUTF(description, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
id = SerializeUtils.readNullableUTF(in);
|
||||
name = SerializeUtils.readNullableUTF(in);
|
||||
description = SerializeUtils.readNullableUTF(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command;
|
||||
|
||||
import org.jetlinks.core.command.CommandSupport;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 命令支持管理提供商工具类,用于提供对{@link CommandSupportManagerProvider}相关通用操作.
|
||||
*
|
||||
* @author zhouhao
|
||||
* @see CommandSupportManagerProvider
|
||||
* @see CommandSupportManagerProviders#getCommandSupport(String, Map)
|
||||
* @since 2.2
|
||||
*/
|
||||
public class CommandSupportManagerProviders {
|
||||
|
||||
|
||||
/**
|
||||
* 根据服务ID获取CommandSupport.
|
||||
* <pre>{@code
|
||||
*
|
||||
* CommandSupportManagerProviders
|
||||
* .getCommandSupport("deviceService:device",Collections.emptyMap())
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param serviceId serviceId 服务名
|
||||
* @return CommandSupport
|
||||
* @see InternalSdkServices
|
||||
* @see org.jetlinks.sdk.server.SdkServices
|
||||
*/
|
||||
public static Mono<CommandSupport> getCommandSupport(String serviceId) {
|
||||
return getCommandSupport(serviceId, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据服务ID和支持ID获取CommandSupport.
|
||||
*
|
||||
* @param serviceId 服务ID
|
||||
* @param supportId 支持ID
|
||||
* @return CommandSupport
|
||||
*/
|
||||
public static Mono<CommandSupport> getCommandSupport(String serviceId, String supportId) {
|
||||
return getProviderNow(serviceId)
|
||||
.getCommandSupport(supportId, Collections.emptyMap())
|
||||
.cast(CommandSupport.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据服务ID获取CommandSupport.
|
||||
* <pre>{@code
|
||||
*
|
||||
* CommandSupportManagerProviders
|
||||
* .getCommandSupport("deviceService:device",Collections.emptyMap())
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param serviceId serviceId 服务名
|
||||
* @param options options
|
||||
* @return CommandSupport
|
||||
* @see InternalSdkServices
|
||||
* @see org.jetlinks.sdk.server.SdkServices
|
||||
*/
|
||||
public static Mono<CommandSupport> getCommandSupport(String serviceId,
|
||||
Map<String, Object> options) {
|
||||
//fast path
|
||||
CommandSupportManagerProvider provider = CommandSupportManagerProvider
|
||||
.supports
|
||||
.get(serviceId)
|
||||
.orElse(null);
|
||||
if (provider != null) {
|
||||
return provider
|
||||
.getCommandSupport(serviceId, options)
|
||||
.cast(CommandSupport.class);
|
||||
}
|
||||
String supportId = serviceId;
|
||||
// deviceService:product
|
||||
if (serviceId.contains(":")) {
|
||||
String[] arr = serviceId.split(":", 2);
|
||||
serviceId = arr[0];
|
||||
supportId = arr[1];
|
||||
}
|
||||
String finalServiceId = serviceId;
|
||||
String finalSupportId = supportId;
|
||||
return Mono.defer(() -> getProviderNow(finalServiceId).getCommandSupport(finalSupportId, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册命令支持
|
||||
*
|
||||
* @param provider {@link CommandSupportManagerProvider#getProvider()}
|
||||
*/
|
||||
public static void register(CommandSupportManagerProvider provider) {
|
||||
CommandSupportManagerProvider.supports.register(provider.getProvider(), provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令支持
|
||||
*
|
||||
* @param provider {@link CommandSupportManagerProvider#getProvider()}
|
||||
* @return Optional
|
||||
*/
|
||||
public static Optional<CommandSupportManagerProvider> getProvider(String provider) {
|
||||
return CommandSupportManagerProvider.supports.get(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令支持,如果不存在则抛出异常{@link UnsupportedOperationException}
|
||||
*
|
||||
* @param provider provider {@link CommandSupportManagerProvider#getProvider()}
|
||||
* @return CommandSupportManagerProvider
|
||||
*/
|
||||
public static CommandSupportManagerProvider getProviderNow(String provider) {
|
||||
return CommandSupportManagerProvider.supports.getNow(provider);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jetlinks.core.command.CommandSupport;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class CompositeCommandSupportManagerProvider implements CommandSupportManagerProvider {
|
||||
private final List<CommandSupportManagerProvider> providers;
|
||||
|
||||
@Override
|
||||
public String getProvider() {
|
||||
return providers.get(0).getProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options) {
|
||||
|
||||
return Flux
|
||||
.fromIterable(providers)
|
||||
.flatMap(provider -> provider.getCommandSupport(id, options))
|
||||
.take(1)
|
||||
.singleOrEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<CommandSupportInfo> getSupportInfo() {
|
||||
return Flux
|
||||
.fromIterable(providers)
|
||||
.flatMap(CommandSupportManagerProvider::getSupportInfo)
|
||||
.distinct(CommandSupportInfo::getId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.hswebframework.web.api.crud.entity.EntityFactoryHolder;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.Permission;
|
||||
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
||||
import org.jetlinks.core.command.AbstractCommandSupport;
|
||||
import org.jetlinks.core.command.Command;
|
||||
import org.jetlinks.core.metadata.FunctionMetadata;
|
||||
import org.jetlinks.core.metadata.SimplePropertyMetadata;
|
||||
import org.jetlinks.core.metadata.types.ArrayType;
|
||||
import org.jetlinks.core.metadata.types.IntType;
|
||||
import org.jetlinks.core.metadata.types.ObjectType;
|
||||
import org.jetlinks.core.metadata.types.StringType;
|
||||
import org.jetlinks.core.utils.Reactors;
|
||||
import org.jetlinks.sdk.server.commons.cmd.*;
|
||||
import org.jetlinks.supports.official.DeviceMetadataParser;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import reactor.bool.BooleanUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用增删改查命令支持,基于{@link ReactiveCrudService}来实现增删改查相关命令
|
||||
*
|
||||
* @param <T> 实体类型
|
||||
* @author zhouhao
|
||||
* @see QueryByIdCommand
|
||||
* @see QueryPagerCommand
|
||||
* @see QueryListCommand
|
||||
* @see CountCommand
|
||||
* @see SaveCommand
|
||||
* @see AddCommand
|
||||
* @see UpdateCommand
|
||||
* @see DeleteCommand
|
||||
* @see DeleteByIdCommand
|
||||
* @since 2.2
|
||||
*/
|
||||
public class CrudCommandSupport<T> extends AbstractCommandSupport {
|
||||
|
||||
final ReactiveCrudService<T, String> service;
|
||||
final ResolvableType _entityType;
|
||||
|
||||
public CrudCommandSupport(ReactiveCrudService<T, String> service) {
|
||||
this(service, ResolvableType
|
||||
.forClass(ReactiveCrudService.class, service.getClass())
|
||||
.getGeneric(0));
|
||||
}
|
||||
|
||||
public CrudCommandSupport(ReactiveCrudService<T, String> service, ResolvableType _entityType) {
|
||||
this.service = service;
|
||||
this._entityType = _entityType;
|
||||
|
||||
registerQueries();
|
||||
registerSaves();
|
||||
registerDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<FunctionMetadata> getCommandMetadata() {
|
||||
return super
|
||||
.getCommandMetadata()
|
||||
.filterWhen(func -> commandIsSupported(func.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> commandIsSupported(String commandId) {
|
||||
|
||||
return BooleanUtils.and(
|
||||
super.commandIsSupported(commandId),
|
||||
hasPermission(getPermissionId(), getAction(commandId))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<FunctionMetadata> getCommandMetadata(String commandId) {
|
||||
return super
|
||||
.getCommandMetadata(commandId)
|
||||
.filterWhen(func -> commandIsSupported(func.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<FunctionMetadata> getCommandMetadata(Command<?> command) {
|
||||
return super
|
||||
.getCommandMetadata(command)
|
||||
.filterWhen(func -> commandIsSupported(func.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<FunctionMetadata> getCommandMetadata(@Nonnull String commandId,
|
||||
@Nullable Map<String, Object> parameters) {
|
||||
return super
|
||||
.getCommandMetadata(commandId, parameters)
|
||||
.filterWhen(func -> commandIsSupported(func.getId()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("all")
|
||||
private T newInstance0() {
|
||||
return (T) _entityType.toClass().getConstructor().newInstance();
|
||||
}
|
||||
|
||||
protected T newInstance() {
|
||||
@SuppressWarnings("all")
|
||||
Class<T> clazz = (Class<T>) _entityType.toClass();
|
||||
return EntityFactoryHolder
|
||||
.newInstance(clazz,
|
||||
this::newInstance0);
|
||||
}
|
||||
|
||||
protected ResolvableType getResolvableType() {
|
||||
return _entityType;
|
||||
}
|
||||
|
||||
protected ObjectType createEntityType() {
|
||||
return (ObjectType) DeviceMetadataParser.withType(_entityType);
|
||||
}
|
||||
|
||||
protected String getPermissionId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Mono<Void> assetPermission(String action) {
|
||||
return assetPermission(getPermissionId(), action);
|
||||
}
|
||||
|
||||
protected Mono<Boolean> hasPermission(String permissionId, String action) {
|
||||
if (permissionId == null) {
|
||||
return Reactors.ALWAYS_TRUE;
|
||||
}
|
||||
return Authentication
|
||||
.currentReactive()
|
||||
.map(auth -> auth.hasPermission(permissionId, action))
|
||||
.defaultIfEmpty(true);
|
||||
}
|
||||
|
||||
protected Mono<Void> assetPermission(String permissionId, String action) {
|
||||
if (permissionId == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Authentication
|
||||
.currentReactive()
|
||||
.flatMap(
|
||||
auth -> auth.hasPermission(permissionId, action)
|
||||
? Mono.empty()
|
||||
: Mono.error(new AccessDenyException.NoStackTrace(permissionId, Collections.singleton(action))));
|
||||
}
|
||||
|
||||
protected String getAction(String commandId) {
|
||||
if (commandId.startsWith("Delete")) {
|
||||
return Permission.ACTION_DELETE;
|
||||
}
|
||||
if (commandId.startsWith("Update") ||
|
||||
commandId.startsWith("Save") ||
|
||||
commandId.startsWith("Add") ||
|
||||
commandId.startsWith("Disable") ||
|
||||
commandId.startsWith("Enable")) {
|
||||
return Permission.ACTION_SAVE;
|
||||
}
|
||||
return Permission.ACTION_QUERY;
|
||||
}
|
||||
|
||||
protected void registerQueries() {
|
||||
//根据id查询
|
||||
registerHandler(
|
||||
QueryByIdCommand
|
||||
.<T>createHandler(
|
||||
metadata -> {
|
||||
metadata
|
||||
.setInputs(Collections.singletonList(
|
||||
SimplePropertyMetadata
|
||||
.of("id", "id", new ArrayType()
|
||||
.elementType(StringType.GLOBAL))
|
||||
));
|
||||
metadata.setOutput(createEntityType());
|
||||
},
|
||||
cmd -> assetPermission(Permission.ACTION_QUERY)
|
||||
.then(service.findById(cmd.getId())),
|
||||
_entityType)
|
||||
);
|
||||
|
||||
//分页查询
|
||||
registerHandler(
|
||||
QueryPagerCommand
|
||||
.<T>createHandler(
|
||||
metadata -> metadata.setOutput(
|
||||
QueryPagerCommand
|
||||
.createOutputType(createEntityType().getProperties())),
|
||||
cmd -> assetPermission(Permission.ACTION_QUERY)
|
||||
.then(service.queryPager(cmd.asQueryParam())),
|
||||
_entityType)
|
||||
);
|
||||
//查询列表
|
||||
registerHandler(
|
||||
QueryListCommand
|
||||
.<T>createHandler(
|
||||
metadata -> metadata.setOutput(createEntityType()),
|
||||
cmd -> assetPermission(Permission.ACTION_QUERY)
|
||||
.thenMany(service.query(cmd.asQueryParam())),
|
||||
_entityType)
|
||||
);
|
||||
//查询数量
|
||||
registerHandler(
|
||||
CountCommand
|
||||
.createHandler(
|
||||
metadata -> metadata.setOutput(new ObjectType()
|
||||
.addProperty("total", "总数", IntType.GLOBAL)),
|
||||
cmd -> assetPermission(Permission.ACTION_QUERY)
|
||||
.then(service.count(cmd.asQueryParam())))
|
||||
);
|
||||
//todo 聚合查询?
|
||||
|
||||
}
|
||||
|
||||
protected void registerSaves() {
|
||||
//批量保存
|
||||
registerHandler(
|
||||
SaveCommand.createHandler(
|
||||
metadata -> {
|
||||
metadata
|
||||
.setInputs(Collections.singletonList(
|
||||
SimplePropertyMetadata.of("data", "数据列表", new ArrayType().elementType(createEntityType()))
|
||||
));
|
||||
metadata.setOutput(createEntityType());
|
||||
},
|
||||
cmd -> {
|
||||
List<T> list = cmd.dataList((data) -> FastBeanCopier.copy(data, newInstance()));
|
||||
return assetPermission(Permission.ACTION_SAVE)
|
||||
.then(service.save(list))
|
||||
.thenMany(Flux.fromIterable(list));
|
||||
},
|
||||
_entityType)
|
||||
);
|
||||
//新增
|
||||
registerHandler(
|
||||
AddCommand
|
||||
.<T>createHandler(
|
||||
metadata -> {
|
||||
metadata
|
||||
.setInputs(Collections.singletonList(
|
||||
SimplePropertyMetadata.of("data", "数据列表", new ArrayType().elementType(createEntityType()))
|
||||
));
|
||||
metadata.setOutput(createEntityType());
|
||||
},
|
||||
cmd -> Flux
|
||||
.fromIterable(cmd.dataList((data) -> FastBeanCopier.copy(data, newInstance())))
|
||||
.as(flux -> assetPermission(Permission.ACTION_SAVE)
|
||||
.then(service.insert(flux))
|
||||
.thenMany(flux)))
|
||||
);
|
||||
//修改
|
||||
registerHandler(
|
||||
UpdateCommand
|
||||
.<T>createHandler(
|
||||
metadata -> {
|
||||
metadata.setInputs(
|
||||
Arrays.asList(
|
||||
SimplePropertyMetadata.of("data", "数据", createEntityType()),
|
||||
QueryCommand.getTermsMetadata()
|
||||
));
|
||||
metadata.setOutput(IntType.GLOBAL);
|
||||
},
|
||||
cmd -> this
|
||||
.assetPermission(Permission.ACTION_SAVE)
|
||||
.then(cmd
|
||||
.applyUpdate(service.createUpdate(), map -> FastBeanCopier.copy(map, newInstance()))
|
||||
.execute()))
|
||||
);
|
||||
}
|
||||
|
||||
protected void registerDelete() {
|
||||
|
||||
//删除
|
||||
registerHandler(
|
||||
DeleteCommand.createHandler(
|
||||
metadata -> {
|
||||
metadata.setInputs(Collections.singletonList(
|
||||
SimplePropertyMetadata.of("terms", "删除条件", QueryCommand.getTermsDataType())));
|
||||
metadata.setOutput(IntType.GLOBAL);
|
||||
},
|
||||
cmd -> this
|
||||
.assetPermission(Permission.ACTION_DELETE)
|
||||
.then(cmd.applyDelete(service.createDelete()).execute()))
|
||||
);
|
||||
//根据id移除
|
||||
registerHandler(
|
||||
DeleteByIdCommand
|
||||
.<Mono<Void>>createHandler(
|
||||
metadata -> {
|
||||
},
|
||||
cmd -> this
|
||||
.assetPermission(Permission.ACTION_DELETE)
|
||||
.then(service.deleteById(cmd.getId()).then()))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command;
|
||||
|
||||
/**
|
||||
* 平台内部的一些服务定义
|
||||
*
|
||||
* @author zhouhao
|
||||
* @see org.jetlinks.sdk.server.SdkServices
|
||||
* @since 2.2
|
||||
*/
|
||||
public interface InternalSdkServices {
|
||||
|
||||
/**
|
||||
* 网络组件服务
|
||||
*/
|
||||
String networkService = "networkService";
|
||||
|
||||
/**
|
||||
* 设备接入网关服务
|
||||
*/
|
||||
String deviceGatewayService = "deviceGatewayService";
|
||||
|
||||
|
||||
/**
|
||||
* 采集器服务
|
||||
*/
|
||||
String collectorService = "collectorService";
|
||||
|
||||
/**
|
||||
* 规则服务
|
||||
*/
|
||||
String ruleService = "ruleService";
|
||||
|
||||
|
||||
/**
|
||||
* 插件服务
|
||||
*/
|
||||
String pluginService = "pluginService";
|
||||
|
||||
/**
|
||||
* 基础服务
|
||||
*/
|
||||
String commonService = "commonService";
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.i18n.LocaleUtils;
|
||||
import org.jetlinks.core.command.AbstractCommandSupport;
|
||||
import org.jetlinks.core.command.CommandSupport;
|
||||
import org.jetlinks.community.annotation.command.CommandService;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用静态命令管理.
|
||||
*
|
||||
* @author zhangji 2024/2/2
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class StaticCommandSupportManagerProvider extends AbstractCommandSupport implements CommandSupportManagerProvider {
|
||||
|
||||
@Getter
|
||||
public String provider;
|
||||
|
||||
private final Map<String, CommandSupport> commandSupports = new HashMap<>();
|
||||
|
||||
public void register(String id, CommandSupport commandSupport) {
|
||||
commandSupports.put(id, commandSupport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options) {
|
||||
CommandSupport cmd = commandSupports.get(id);
|
||||
if (cmd == null) {
|
||||
return getUndefined(id, options);
|
||||
}
|
||||
return Mono.just(cmd);
|
||||
}
|
||||
|
||||
protected Mono<? extends CommandSupport> getUndefined(String id, Map<String, Object> options) {
|
||||
return Mono.just(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<CommandSupportInfo> getSupportInfo() {
|
||||
Flux<CommandSupportInfo> another = Flux
|
||||
.fromIterable(commandSupports.entrySet())
|
||||
.map(entry -> createCommandSupport(entry.getKey(), entry.getValue().getClass()));
|
||||
|
||||
if (!this.handlers.isEmpty()) {
|
||||
return Flux.concat(another, Flux.just(createCommandSupport(null, this.getClass())));
|
||||
}
|
||||
return another;
|
||||
}
|
||||
|
||||
protected final CommandSupportInfo createCommandSupport(String id, Class<?> clazz) {
|
||||
String name = id;
|
||||
String description = null;
|
||||
Schema schema = AnnotationUtils.findAnnotation(clazz, Schema.class);
|
||||
if (null != schema) {
|
||||
name = schema.title();
|
||||
description = schema.description();
|
||||
}
|
||||
CommandService service = AnnotationUtils.findAnnotation(clazz, CommandService.class);
|
||||
if (null != service) {
|
||||
name = LocaleUtils.resolveMessage(service.name(), service.name());
|
||||
description = String.join("", service.description());
|
||||
}
|
||||
return CommandSupportInfo.of(id, name, description);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.crud;
|
||||
|
||||
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
||||
|
||||
public interface CrudCommandHandler<T, PK>
|
||||
extends QueryCommandHandler<T, PK>, SaveCommandHandler<T, PK>, DeleteCommandHandler<T, PK> {
|
||||
|
||||
@Override
|
||||
ReactiveCrudService<T, PK> getService();
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.crud;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import org.hswebframework.web.authorization.annotation.SaveAction;
|
||||
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
||||
import org.jetlinks.core.annotation.command.CommandHandler;
|
||||
import org.jetlinks.sdk.server.commons.cmd.DeleteByIdCommand;
|
||||
import org.jetlinks.sdk.server.commons.cmd.DeleteCommand;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface DeleteCommandHandler<T, PK> {
|
||||
|
||||
ReactiveCrudService<T, PK> getService();
|
||||
|
||||
@CommandHandler
|
||||
@SaveAction
|
||||
default Mono<Integer> deleteById(DeleteByIdCommand<Integer> command) {
|
||||
return getService()
|
||||
.deleteById(Flux.fromIterable(
|
||||
Collections2
|
||||
.transform(command.getIdList(),
|
||||
v -> (PK) v)));
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@SaveAction
|
||||
default Mono<Integer> delete(DeleteCommand command) {
|
||||
return command
|
||||
.applyDelete(getService().createDelete())
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.crud;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import org.hswebframework.web.api.crud.entity.PagerResult;
|
||||
import org.hswebframework.web.authorization.annotation.QueryAction;
|
||||
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
||||
import org.jetlinks.core.annotation.command.CommandHandler;
|
||||
import org.jetlinks.sdk.server.commons.cmd.CountCommand;
|
||||
import org.jetlinks.sdk.server.commons.cmd.QueryByIdCommand;
|
||||
import org.jetlinks.sdk.server.commons.cmd.QueryListCommand;
|
||||
import org.jetlinks.sdk.server.commons.cmd.QueryPagerCommand;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface QueryCommandHandler<T, PK> {
|
||||
|
||||
ReactiveCrudService<T, PK> getService();
|
||||
|
||||
@CommandHandler
|
||||
@QueryAction
|
||||
default Flux<T> queryById(QueryByIdCommand<T> command) {
|
||||
return getService()
|
||||
.findById(
|
||||
Collections2.transform(command.getIdList(),
|
||||
v -> (PK) v)
|
||||
);
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@QueryAction
|
||||
default Flux<T> queryList(QueryListCommand<T> command) {
|
||||
return getService().query(command.asQueryParam());
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@QueryAction
|
||||
default Mono<PagerResult<T>> queryPager(QueryPagerCommand<T> command) {
|
||||
return getService().queryPager(command.asQueryParam());
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@QueryAction
|
||||
default Mono<Integer> count(CountCommand command) {
|
||||
return getService().count(command.asQueryParam());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.crud;
|
||||
|
||||
import org.hswebframework.web.authorization.annotation.SaveAction;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
||||
import org.jetlinks.core.annotation.command.CommandHandler;
|
||||
import org.jetlinks.sdk.server.commons.cmd.AddCommand;
|
||||
import org.jetlinks.sdk.server.commons.cmd.SaveCommand;
|
||||
import org.jetlinks.sdk.server.commons.cmd.UpdateCommand;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SaveCommandHandler<T, PK> {
|
||||
|
||||
ReactiveCrudService<T, PK> getService();
|
||||
|
||||
default T convertData(Object data) {
|
||||
return FastBeanCopier.copy(data, getService().getRepository().newInstanceNow());
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@SaveAction
|
||||
default Flux<T> save(SaveCommand<T> command) {
|
||||
List<T> data = command.dataList(this::convertData);
|
||||
return getService()
|
||||
.save(data)
|
||||
.thenMany(Flux.fromIterable(data));
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@SaveAction
|
||||
default Flux<T> add(AddCommand<T> command) {
|
||||
List<T> data = command.dataList(this::convertData);
|
||||
return getService()
|
||||
.save(data)
|
||||
.thenMany(Flux.fromIterable(data));
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
@SaveAction
|
||||
default Mono<Integer> update(UpdateCommand<T> command) {
|
||||
return command
|
||||
.applyUpdate(getService().createUpdate(),
|
||||
this::convertData)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.register;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetlinks.community.command.CommandSupportManagerProvider;
|
||||
import org.jetlinks.community.command.CommandSupportManagerProviders;
|
||||
import org.jetlinks.community.command.CompositeCommandSupportManagerProvider;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.jetlinks.community.annotation.command.CommandService;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class CommandServiceEndpointRegister implements ApplicationContextAware, SmartInitializingSingleton {
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
Map<String, Object> beans = context.getBeansWithAnnotation(CommandService.class);
|
||||
|
||||
//静态Provider
|
||||
Map<String, List<CommandSupportManagerProvider>> statics = context
|
||||
.getBeanProvider(CommandSupportManagerProvider.class)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(CommandSupportManagerProvider::getProvider));
|
||||
|
||||
Map<String, SpringBeanCommandSupportProvider> providers = new HashMap<>();
|
||||
|
||||
for (Object value : beans.values()) {
|
||||
CommandService endpoint =
|
||||
AnnotatedElementUtils.findMergedAnnotation(ClassUtils.getUserClass(value), CommandService.class);
|
||||
if (endpoint == null || !endpoint.autoRegistered()) {
|
||||
continue;
|
||||
}
|
||||
String id = endpoint.id();
|
||||
String support = id;
|
||||
if (id.contains(":")) {
|
||||
support = id.substring(id.indexOf(":") + 1);
|
||||
id = id.substring(0, id.indexOf(":"));
|
||||
}
|
||||
|
||||
SpringBeanCommandSupportProvider provider = providers
|
||||
.computeIfAbsent(id, SpringBeanCommandSupportProvider::new);
|
||||
log.debug("register command support:{} -> {}", endpoint.id(), value);
|
||||
provider.register(support, endpoint, value);
|
||||
}
|
||||
|
||||
for (SpringBeanCommandSupportProvider value : providers.values()) {
|
||||
if (value.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
//合并静态Provider
|
||||
List<CommandSupportManagerProvider> provider = statics.remove(value.getProvider());
|
||||
if (provider != null) {
|
||||
provider.forEach(value::register);
|
||||
}
|
||||
|
||||
CommandSupportManagerProviders.register(value);
|
||||
}
|
||||
for (List<CommandSupportManagerProvider> value : statics.values()) {
|
||||
if (value.size() == 1) {
|
||||
CommandSupportManagerProviders.register(value.get(0));
|
||||
} else {
|
||||
CommandSupportManagerProviders.register(new CompositeCommandSupportManagerProvider(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.register;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.i18n.LocaleUtils;
|
||||
import org.jetlinks.core.command.CommandSupport;
|
||||
import org.jetlinks.core.command.CompositeCommandSupport;
|
||||
import org.jetlinks.community.annotation.command.CommandService;
|
||||
import org.jetlinks.community.command.CommandSupportManagerProvider;
|
||||
import org.jetlinks.supports.command.JavaBeanCommandSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
class SpringBeanCommandSupportProvider implements CommandSupportManagerProvider {
|
||||
|
||||
private final String provider;
|
||||
private final Map<String, CompositeSpringBeanCommandSupport> commandSupports = new HashMap<>();
|
||||
|
||||
private final List<CommandSupportManagerProvider> statics = new ArrayList<>();
|
||||
|
||||
public SpringBeanCommandSupportProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
void register(CommandSupportManagerProvider provider) {
|
||||
statics.add(provider);
|
||||
}
|
||||
|
||||
void register(String support, CommandService annotation, Object bean) {
|
||||
Objects.requireNonNull(annotation, "endpoint");
|
||||
Objects.requireNonNull(bean, "bean");
|
||||
|
||||
SpringBeanCommandSupport commandSupport = new SpringBeanCommandSupport(annotation, bean);
|
||||
if (commandSupport.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
//相同support合并成一个
|
||||
commandSupports
|
||||
.computeIfAbsent(support, id -> new CompositeSpringBeanCommandSupport(id,provider))
|
||||
.register(commandSupport);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return commandSupports.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options) {
|
||||
CommandSupport support = commandSupports.get(StringUtils.hasText(id) ? id : provider);
|
||||
if (support != null) {
|
||||
return Mono.just(support);
|
||||
}
|
||||
if (statics.isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Flux
|
||||
.fromIterable(statics)
|
||||
.flatMap(provider -> provider.getCommandSupport(id, options))
|
||||
.take(1)
|
||||
.singleOrEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<CommandSupportInfo> getSupportInfo() {
|
||||
if (statics.isEmpty()) {
|
||||
return Flux
|
||||
.fromIterable(commandSupports.values())
|
||||
.flatMapIterable(CompositeSpringBeanCommandSupport::getInfo);
|
||||
}
|
||||
return Flux
|
||||
.concat(
|
||||
Flux
|
||||
.fromIterable(commandSupports.values())
|
||||
.flatMapIterable(CompositeSpringBeanCommandSupport::getInfo),
|
||||
Flux.fromIterable(statics)
|
||||
.flatMap(CommandSupportManagerProvider::getSupportInfo)
|
||||
)
|
||||
.distinct(info -> {
|
||||
String id = info.getId();
|
||||
return String.valueOf(id);
|
||||
});
|
||||
}
|
||||
|
||||
static class CompositeSpringBeanCommandSupport extends CompositeCommandSupport {
|
||||
private final String id;
|
||||
private final String provider;
|
||||
|
||||
public CompositeSpringBeanCommandSupport(String id,String provider) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public List<CommandSupportInfo> getInfo() {
|
||||
return Lists
|
||||
.transform(
|
||||
getSupports(),
|
||||
support -> {
|
||||
SpringBeanCommandSupport commandSupport = support.unwrap(SpringBeanCommandSupport.class);
|
||||
//兼容为null
|
||||
String _id = id.equals(provider) ? null : id;
|
||||
return CommandSupportInfo.of(
|
||||
_id,
|
||||
LocaleUtils.resolveMessage(commandSupport.annotation.name(), commandSupport.annotation.name()),
|
||||
String.join("", commandSupport.annotation.description())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getSupports().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
static class SpringBeanCommandSupport extends JavaBeanCommandSupport {
|
||||
private final CommandService annotation;
|
||||
|
||||
boolean isEmpty() {
|
||||
return handlers.isEmpty();
|
||||
}
|
||||
|
||||
public SpringBeanCommandSupport(CommandService annotation, Object target) {
|
||||
super(target);
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.core.command.AbstractCommand;
|
||||
import org.jetlinks.core.command.CommandMetadataResolver;
|
||||
import org.jetlinks.core.command.CommandUtils;
|
||||
import org.jetlinks.core.metadata.FunctionMetadata;
|
||||
import org.jetlinks.core.metadata.SimpleFunctionMetadata;
|
||||
import org.jetlinks.community.command.rule.data.RelieveInfo;
|
||||
import org.jetlinks.community.command.rule.data.RelieveResult;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Schema(title = "解除告警命令")
|
||||
public class RelievedAlarmCommand extends AbstractCommand<Mono<RelieveResult>,RelievedAlarmCommand> {
|
||||
|
||||
@Schema(description = "解除告警传参信息")
|
||||
public RelieveInfo getRelieveInfo() {
|
||||
return FastBeanCopier.copy(readable(), new RelieveInfo());
|
||||
}
|
||||
|
||||
public RelievedAlarmCommand setRelieveInfo(RelieveInfo relieveInfo) {
|
||||
return with(FastBeanCopier.copy(relieveInfo, writable()));
|
||||
}
|
||||
|
||||
public static FunctionMetadata metadata() {
|
||||
SimpleFunctionMetadata metadata = new SimpleFunctionMetadata();
|
||||
metadata.setId(CommandUtils.getCommandIdByType(RelievedAlarmCommand.class));
|
||||
metadata.setName("解除告警命令");
|
||||
metadata.setInputs(CommandMetadataResolver.resolveInputs(ResolvableType.forClass(RelieveInfo.class)));
|
||||
metadata.setOutput(CommandMetadataResolver.resolveOutput(ResolvableType.forClass(RelievedAlarmCommand.class)));
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule;
|
||||
|
||||
public interface RuleCommandServices {
|
||||
/**
|
||||
* 场景
|
||||
*/
|
||||
String sceneService = "sceneService";
|
||||
|
||||
/**
|
||||
* 告警配置
|
||||
*/
|
||||
String alarmConfigService = "alarmConfigService";
|
||||
|
||||
/**
|
||||
* 告警记录
|
||||
*/
|
||||
String alarmRecordService = "alarmRecordService";
|
||||
|
||||
/**
|
||||
* 告警历史
|
||||
*/
|
||||
String alarmHistoryService = "alarmHistoryService";
|
||||
|
||||
/**
|
||||
* 告警规则绑定
|
||||
*/
|
||||
String alarmRuleBindService = "alarmRuleBindService";
|
||||
|
||||
/**
|
||||
* 告警相关
|
||||
*/
|
||||
String alarm = "alarm";
|
||||
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.core.command.AbstractCommand;
|
||||
import org.jetlinks.core.command.CommandMetadataResolver;
|
||||
import org.jetlinks.core.command.CommandUtils;
|
||||
import org.jetlinks.core.metadata.FunctionMetadata;
|
||||
import org.jetlinks.core.metadata.SimpleFunctionMetadata;
|
||||
import org.jetlinks.community.command.rule.data.AlarmInfo;
|
||||
import org.jetlinks.community.command.rule.data.AlarmResult;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
@Schema(title = "触发告警命令")
|
||||
public class TriggerAlarmCommand extends AbstractCommand<Mono<AlarmResult>,TriggerAlarmCommand> {
|
||||
private static final long serialVersionUID = 7056867872399432831L;
|
||||
|
||||
|
||||
@Schema(description = "告警传参信息")
|
||||
public AlarmInfo getAlarmInfo() {
|
||||
return FastBeanCopier.copy(readable(), new AlarmInfo());
|
||||
}
|
||||
|
||||
public TriggerAlarmCommand setAlarmInfo(AlarmInfo alarmInfo) {
|
||||
return with(FastBeanCopier.copy(alarmInfo, writable()));
|
||||
}
|
||||
|
||||
public static FunctionMetadata metadata() {
|
||||
SimpleFunctionMetadata metadata = new SimpleFunctionMetadata();
|
||||
metadata.setId(CommandUtils.getCommandIdByType(TriggerAlarmCommand.class));
|
||||
metadata.setName("触发告警命令");
|
||||
metadata.setInputs(CommandMetadataResolver.resolveInputs(ResolvableType.forClass(AlarmInfo.class)));
|
||||
metadata.setOutput(CommandMetadataResolver.resolveOutput(ResolvableType.forClass(TriggerAlarmCommand.class)));
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule.data;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetlinks.community.terms.TermSpec;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 触发告警参数
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AlarmInfo implements Serializable {
|
||||
private static final long serialVersionUID = -2316376361116648370L;
|
||||
|
||||
@Schema(description = "告警配置ID")
|
||||
private String alarmConfigId;
|
||||
|
||||
@Schema(description = "告警名称")
|
||||
private String alarmName;
|
||||
|
||||
@Schema(description = "告警说明")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "告警级别")
|
||||
private int level;
|
||||
|
||||
@Schema(description = "告警目标类型")
|
||||
private String targetType;
|
||||
|
||||
@Schema(description = "告警目标ID")
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "告警目标名称")
|
||||
private String targetName;
|
||||
|
||||
@Schema(description = "告警来源类型")
|
||||
private String sourceType;
|
||||
|
||||
@Schema(description = "告警来源ID")
|
||||
private String sourceId;
|
||||
|
||||
@Schema(description = "告警来源的创建人ID")
|
||||
private String sourceCreatorId;
|
||||
|
||||
@Schema(description = "告警来源名称")
|
||||
private String sourceName;
|
||||
|
||||
/**
|
||||
* 标识告警触发的配置来自什么业务功能
|
||||
*/
|
||||
@Schema(description = "告警配置源")
|
||||
private String alarmConfigSource;
|
||||
|
||||
@Schema(description = "告警数据")
|
||||
private Map<String, Object> data;
|
||||
|
||||
/**
|
||||
* 告警触发条件
|
||||
*/
|
||||
private TermSpec termSpec;
|
||||
|
||||
@Schema(description = "告警时间")
|
||||
private Long alarmTime;
|
||||
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule.data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 告警结果
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class AlarmResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -1752497262936740164L;
|
||||
|
||||
@Schema(description = "告警ID")
|
||||
private String recordId;
|
||||
|
||||
@Schema(description = "是否重复告警")
|
||||
private boolean alarming;
|
||||
|
||||
@Schema(description = "当前首次触发")
|
||||
private boolean firstAlarm;
|
||||
|
||||
@Schema(description = "上一次告警时间")
|
||||
private long lastAlarmTime;
|
||||
|
||||
@Schema(description = "首次告警或者解除告警后的再一次告警时间")
|
||||
private long alarmTime;
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule.data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 解除告警参数
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RelieveInfo extends AlarmInfo{
|
||||
|
||||
@Schema(description = "解除原因")
|
||||
private String relieveReason;
|
||||
|
||||
@Schema(description = "解除时间")
|
||||
private Long relieveTime;
|
||||
|
||||
@Schema(description = "解除说明")
|
||||
private String describe;
|
||||
|
||||
/**
|
||||
* 告警解除类型,人工(user)、系统(system)等
|
||||
*/
|
||||
@Schema(description = "告警解除类型")
|
||||
private String alarmRelieveType;
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.command.rule.data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 解除警告结果
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RelieveResult extends AlarmResult{
|
||||
|
||||
@Schema(description = "告警级别")
|
||||
private int level;
|
||||
|
||||
@Schema(description = "告警原因描述")
|
||||
private String actualDesc;
|
||||
|
||||
@Schema(description = "解除原因")
|
||||
private String relieveReason;
|
||||
|
||||
@Schema(description = "解除时间")
|
||||
private long relieveTime;
|
||||
|
||||
@Schema(description = "解除说明")
|
||||
private String describe;
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
import org.jetlinks.community.ValueObject;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 配置管理器,统一管理系统相关配置信息
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ConfigManager {
|
||||
|
||||
/**
|
||||
* 获取全部已经定义的配置作用域
|
||||
*
|
||||
* @return 配置作用域
|
||||
*/
|
||||
Flux<ConfigScope> getScopes();
|
||||
|
||||
/**
|
||||
* 获取根据作用域ID获取已经定义的配置作用域
|
||||
*
|
||||
* @return 配置作用域
|
||||
*/
|
||||
Mono<ConfigScope> getScope(String scope);
|
||||
|
||||
/**
|
||||
* 获取指定作用域下的属性定义信息
|
||||
*
|
||||
* @param scope 配置作用域
|
||||
* @return 属性定义信息
|
||||
*/
|
||||
Flux<ConfigPropertyDef> getPropertyDef(String scope);
|
||||
|
||||
/**
|
||||
* 获取作用于下的全部配置
|
||||
*
|
||||
* @param scope 配置作用域
|
||||
* @return 配置信息
|
||||
*/
|
||||
Mono<ValueObject> getProperties(String scope);
|
||||
|
||||
/**
|
||||
* 设置作用域下的配置
|
||||
*
|
||||
* @param scope 作用域
|
||||
* @param values 配置信息
|
||||
* @return void
|
||||
*/
|
||||
Mono<Void> setProperties(String scope, Map<String, Object> values);
|
||||
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import org.jetlinks.core.metadata.types.StringType;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(of = "key")
|
||||
public class ConfigPropertyDef {
|
||||
|
||||
@Schema(description = "配置key")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "配置名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否只读")
|
||||
private boolean readonly;
|
||||
|
||||
@Schema(description = "配置类型")
|
||||
private String type = StringType.ID;
|
||||
|
||||
@Schema(description = "默认值")
|
||||
private String defaultValue;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class ConfigScope {
|
||||
|
||||
@Schema(description = "ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否公开访问(不需要登录)")
|
||||
private boolean publicAccess;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
/**
|
||||
* 实现此接口,自定义配置域以及配置定义
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ConfigScopeCustomizer {
|
||||
|
||||
/**
|
||||
* 执行自定义,通过manager来添加自定义作用域
|
||||
*
|
||||
* @param manager manager
|
||||
*/
|
||||
void custom(ConfigScopeManager manager);
|
||||
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ConfigScopeManager {
|
||||
|
||||
void addScope(ConfigScope scope, List<ConfigPropertyDef> properties);
|
||||
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationProperties(prefix = "system.config")
|
||||
public class ConfigScopeProperties implements ConfigScopeCustomizer{
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<Scope> scopes = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void custom(ConfigScopeManager manager) {
|
||||
for (Scope scope : scopes) {
|
||||
manager.addScope(FastBeanCopier.copy(scope,new ConfigScope()), scope.properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Scope extends ConfigScope {
|
||||
private List<ConfigPropertyDef> properties = new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
|
||||
import org.hswebframework.web.cache.ReactiveCache;
|
||||
import org.hswebframework.web.cache.ReactiveCacheManager;
|
||||
import org.jetlinks.community.ValueObject;
|
||||
import org.jetlinks.community.config.entity.ConfigEntity;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class SimpleConfigManager implements ConfigManager, ConfigScopeManager {
|
||||
|
||||
private final Map<ConfigScope, Set<ConfigPropertyDef>> scopes = new ConcurrentHashMap<>();
|
||||
|
||||
private final ReactiveRepository<ConfigEntity, String> repository;
|
||||
|
||||
private final ReactiveCache<Map<String, Object>> cache;
|
||||
|
||||
public SimpleConfigManager(ReactiveRepository<ConfigEntity, String> repository, ReactiveCacheManager cacheManager) {
|
||||
this.repository = repository;
|
||||
this.cache = cacheManager.getCache("system-config");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScope(ConfigScope scope,
|
||||
List<ConfigPropertyDef> properties) {
|
||||
scopes.computeIfAbsent(scope, ignore -> new LinkedHashSet<>())
|
||||
.addAll(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ConfigScope> getScopes() {
|
||||
return Flux.fromIterable(scopes.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ConfigScope> getScope(String scope) {
|
||||
return this
|
||||
.getScopes()
|
||||
.filter(configScope -> Objects.equals(configScope.getId(), scope))
|
||||
.take(1)
|
||||
.singleOrEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ConfigPropertyDef> getPropertyDef(String scope) {
|
||||
return Flux.fromIterable(scopes.getOrDefault(
|
||||
ConfigScope.of(scope, scope, false),
|
||||
Collections.emptySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ValueObject> getProperties(String scope) {
|
||||
return Mono
|
||||
.zip(
|
||||
//默认值
|
||||
getPropertyDef(scope)
|
||||
.filter(def -> null != def.getDefaultValue())
|
||||
.collectMap(ConfigPropertyDef::getKey, ConfigPropertyDef::getDefaultValue),
|
||||
//数据库配置的值
|
||||
cache
|
||||
.getMono(scope, () -> getPropertiesNow(scope)),
|
||||
(defaults, values) -> {
|
||||
Map<String, Object> properties = new HashMap<>(values);
|
||||
defaults.forEach(properties::putIfAbsent);
|
||||
return properties;
|
||||
}
|
||||
)
|
||||
.map(ValueObject::of);
|
||||
}
|
||||
|
||||
private Mono<Map<String, Object>> getPropertiesNow(String scope) {
|
||||
return repository
|
||||
.createQuery()
|
||||
.where(ConfigEntity::getScope, scope)
|
||||
.fetch()
|
||||
.filter(val -> MapUtils.isNotEmpty(val.getProperties()))
|
||||
.reduce(new LinkedHashMap<>(), (l, r) -> {
|
||||
l.putAll(r.getProperties());
|
||||
return l;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> setProperties(String scope, Map<String, Object> values) {
|
||||
ConfigEntity entity = new ConfigEntity();
|
||||
entity.setProperties(values);
|
||||
entity.setScope(scope);
|
||||
entity.getId();
|
||||
return repository
|
||||
.save(entity)
|
||||
.then(cache.evict(scope));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
|
||||
import org.hswebframework.web.api.crud.entity.GenericEntity;
|
||||
import org.hswebframework.web.crud.annotation.EnableEntityEvent;
|
||||
import org.hswebframework.web.utils.DigestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import java.sql.JDBCType;
|
||||
import java.util.Map;
|
||||
|
||||
@Table(name = "s_config", indexes = {
|
||||
@Index(name = "idx_conf_scope", columnList = "scope")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@EnableEntityEvent
|
||||
public class ConfigEntity extends GenericEntity<String> {
|
||||
|
||||
@Column(length = 64, nullable = false, updatable = false)
|
||||
@Schema(description = "作用域")
|
||||
private String scope;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Schema
|
||||
@JsonCodec
|
||||
@ColumnType(jdbcType = JDBCType.LONGVARCHAR)
|
||||
private Map<String,Object> properties;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
if (!StringUtils.hasText(super.getId())) {
|
||||
setId(generateId(scope));
|
||||
}
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
public static String generateId(String scope) {
|
||||
return DigestUtils.md5Hex(String.join("|", scope));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config.verification;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.hswebframework.web.crud.events.EntitySavedEvent;
|
||||
import org.hswebframework.web.exception.BusinessException;
|
||||
import org.jetlinks.community.config.entity.ConfigEntity;
|
||||
import org.jetlinks.reactor.ql.utils.CastUtils;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* @author bestfeng
|
||||
*/
|
||||
@RestController
|
||||
public class ConfigVerificationService {
|
||||
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private static final String PATH_VERIFICATION_URI = "/system/config/base-path/verification";
|
||||
|
||||
public ConfigVerificationService() {
|
||||
this.webClient = WebClient
|
||||
.builder()
|
||||
.build();
|
||||
}
|
||||
|
||||
@GetMapping(value = PATH_VERIFICATION_URI)
|
||||
@Operation(description = "basePath配置验证接口")
|
||||
public Mono<String> basePathValidate() {
|
||||
return Mono.just("auth:"+PATH_VERIFICATION_URI);
|
||||
}
|
||||
|
||||
|
||||
@EventListener
|
||||
public void handleConfigSavedEvent(EntitySavedEvent<ConfigEntity> event){
|
||||
//base-path校验
|
||||
event.async(
|
||||
Flux.fromIterable(event.getEntity())
|
||||
.filter(config -> Objects.equals(config.getScope(), "paths"))
|
||||
.flatMap(config-> doBasePathValidate(config.getProperties().get("base-path")))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public Mono<Void> doBasePathValidate(Object basePath) {
|
||||
if (basePath == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
URI uri = URI.create(CastUtils.castString(CastUtils.castString(basePath).concat(PATH_VERIFICATION_URI)));
|
||||
if (Objects.equals(uri.getHost(), "127.0.0.1")){
|
||||
return Mono.error(new BusinessException("error.base_path_host_error", 500, "127.0.0.1"));
|
||||
}
|
||||
if (Objects.equals(uri.getHost(), "localhost")){
|
||||
return Mono.error(new BusinessException("error.base_path_host_error", 500, "localhost"));
|
||||
}
|
||||
|
||||
return webClient
|
||||
.get()
|
||||
.uri(uri)
|
||||
.exchangeToMono(cr -> {
|
||||
if (cr.statusCode().is2xxSuccessful()) {
|
||||
return cr.bodyToMono(String.class)
|
||||
.filter(r-> r.contains("auth:"+PATH_VERIFICATION_URI))
|
||||
.switchIfEmpty(Mono.error(()-> new BusinessException("error.base_path_error")));
|
||||
}
|
||||
return Mono.defer(() -> Mono.error(new BusinessException("error.base_path_error")));
|
||||
})
|
||||
.timeout(Duration.ofSeconds(3), Mono.error(TimeoutException::new))
|
||||
.onErrorResume(err -> {
|
||||
while (err != null) {
|
||||
if (err instanceof TimeoutException) {
|
||||
return Mono.error(() -> new BusinessException("error.base_path_validate_request_timeout"));
|
||||
} else if (err instanceof UnknownHostException) {
|
||||
return Mono.error(() -> new BusinessException("error.base_path_DNS_resolution_failed"));
|
||||
}
|
||||
err = err.getCause();
|
||||
}
|
||||
return Mono.error(() -> new BusinessException("error.base_path_error"));
|
||||
})
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.config.web;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.annotation.Authorize;
|
||||
import org.hswebframework.web.authorization.annotation.QueryAction;
|
||||
import org.hswebframework.web.authorization.annotation.Resource;
|
||||
import org.hswebframework.web.authorization.annotation.SaveAction;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.jetlinks.community.ValueObject;
|
||||
import org.jetlinks.community.config.ConfigManager;
|
||||
import org.jetlinks.community.config.ConfigPropertyDef;
|
||||
import org.jetlinks.community.config.ConfigScope;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/system/config")
|
||||
@Resource(id = "system_config", name = "系统配置管理")
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "系统配置管理")
|
||||
public class SystemConfigManagerController {
|
||||
|
||||
private final ConfigManager configManager;
|
||||
|
||||
@GetMapping("/scopes")
|
||||
@QueryAction
|
||||
@Operation(summary = "获取配置作用域")
|
||||
public Flux<ConfigScope> getConfigScopes() {
|
||||
return configManager.getScopes();
|
||||
}
|
||||
|
||||
@GetMapping("/{scope}")
|
||||
@Authorize(ignore = true)
|
||||
@Operation(summary = "获取作用域下的全部配置信息")
|
||||
public Mono<Map<String, Object>> getConfigs(@PathVariable String scope) {
|
||||
return Authentication
|
||||
.currentReactive()
|
||||
.hasElement()
|
||||
.flatMap(hasAuth -> configManager
|
||||
.getScope(scope)
|
||||
//公共访问配置或者用户已登录
|
||||
.map(conf -> conf.isPublicAccess() || hasAuth)
|
||||
//没有定义配置,则用户登录即可访问
|
||||
.defaultIfEmpty(hasAuth)
|
||||
.filter(Boolean::booleanValue)
|
||||
.flatMap(ignore -> configManager.getProperties(scope))
|
||||
.map(ValueObject::values))
|
||||
.defaultIfEmpty(Collections.emptyMap());
|
||||
}
|
||||
|
||||
@GetMapping("/{scope}/_detail")
|
||||
@QueryAction
|
||||
@Operation(summary = "获取作用域下的配置信息")
|
||||
public Flux<ConfigPropertyValue> getConfigDetail(@PathVariable String scope) {
|
||||
return configManager
|
||||
.getProperties(scope)
|
||||
.flatMapMany(values -> configManager
|
||||
.getPropertyDef(scope)
|
||||
.map(def -> ConfigPropertyValue.of(def, values.get(def.getKey()).orElse(null))));
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/scopes")
|
||||
@QueryAction
|
||||
@Operation(summary = "获取作用域下的配置详情")
|
||||
public Flux<Scope> getConfigDetail(@RequestBody Mono<List<String>> scopeMono) {
|
||||
return scopeMono
|
||||
.flatMapMany(scopes -> Flux
|
||||
.fromIterable(scopes)
|
||||
.flatMap(scope -> getConfigs(scope)
|
||||
.map(properties -> new Scope(scope, properties))));
|
||||
}
|
||||
|
||||
@PostMapping("/{scope}")
|
||||
@SaveAction
|
||||
@Operation(summary = "保存配置")
|
||||
public Mono<Void> saveConfig(@PathVariable String scope,
|
||||
@RequestBody Mono<Map<String, Object>> properties) {
|
||||
return properties.flatMap(props -> configManager.setProperties(scope, props));
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/scope/_save")
|
||||
@SaveAction
|
||||
@Operation(summary = "批量保存配置")
|
||||
@Transactional
|
||||
public Mono<Void> saveConfig(@RequestBody Flux<Scope> scope) {
|
||||
|
||||
return scope
|
||||
.concatMap(scopeConfig -> configManager.setProperties(scopeConfig.getScope(), scopeConfig.getProperties()))
|
||||
.then();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ConfigPropertyValue extends ConfigPropertyDef {
|
||||
private Object value;
|
||||
|
||||
public static ConfigPropertyValue of(ConfigPropertyDef def, Object value) {
|
||||
ConfigPropertyValue val = FastBeanCopier.copy(def, new ConfigPropertyValue());
|
||||
val.setValue(value);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Scope {
|
||||
|
||||
private String scope;
|
||||
|
||||
private Map<String, Object> properties;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,95 +1,23 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.configuration;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.Generated;
|
||||
import org.apache.commons.beanutils.BeanUtilsBean;
|
||||
import org.apache.commons.beanutils.Converter;
|
||||
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.hswebframework.web.cache.ReactiveCacheManager;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
import org.hswebframework.web.dict.defaults.DefaultItemDefine;
|
||||
import org.jetlinks.community.Interval;
|
||||
import org.jetlinks.community.JvmErrorException;
|
||||
import org.jetlinks.community.command.CommandSupportManagerProvider;
|
||||
import org.jetlinks.community.command.CommandSupportManagerProviders;
|
||||
import org.jetlinks.community.command.register.CommandServiceEndpointRegister;
|
||||
import org.jetlinks.community.config.ConfigManager;
|
||||
import org.jetlinks.community.config.ConfigScopeCustomizer;
|
||||
import org.jetlinks.community.config.ConfigScopeProperties;
|
||||
import org.jetlinks.community.config.SimpleConfigManager;
|
||||
import org.jetlinks.community.config.entity.ConfigEntity;
|
||||
import org.jetlinks.community.dictionary.DictionaryJsonDeserializer;
|
||||
import org.jetlinks.community.form.type.FieldTypeProvider;
|
||||
import org.jetlinks.community.reactorql.aggregation.InternalAggregationSupports;
|
||||
import org.jetlinks.community.reactorql.function.InternalFunctionSupport;
|
||||
import org.jetlinks.community.reactorql.term.TermTypeSupport;
|
||||
import org.jetlinks.community.reactorql.term.TermTypes;
|
||||
import org.jetlinks.community.reference.DataReferenceManager;
|
||||
import org.jetlinks.community.reference.DataReferenceProvider;
|
||||
import org.jetlinks.community.reference.DefaultDataReferenceManager;
|
||||
import org.jetlinks.community.resource.DefaultResourceManager;
|
||||
import org.jetlinks.community.resource.ResourceManager;
|
||||
import org.jetlinks.community.resource.ResourceProvider;
|
||||
import org.jetlinks.community.resource.TypeScriptDeclareResourceProvider;
|
||||
import org.jetlinks.community.resource.initialize.PermissionResourceProvider;
|
||||
import org.jetlinks.community.service.DefaultUserBindService;
|
||||
import org.jetlinks.community.utils.TimeUtils;
|
||||
import org.jetlinks.core.metadata.DataType;
|
||||
import org.jetlinks.core.metadata.types.DataTypes;
|
||||
import org.jetlinks.reactor.ql.feature.Feature;
|
||||
import org.jetlinks.reactor.ql.supports.DefaultReactorQLMetadata;
|
||||
import org.jetlinks.reactor.ql.utils.CastUtils;
|
||||
import org.jetlinks.supports.official.JetLinksDataTypeCodecs;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Hooks;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@AutoConfiguration
|
||||
@Configuration
|
||||
@SuppressWarnings("all")
|
||||
@EnableConfigurationProperties({ConfigScopeProperties.class})
|
||||
public class CommonConfiguration {
|
||||
|
||||
static {
|
||||
InternalAggregationSupports.register();
|
||||
InternalFunctionSupport.register();
|
||||
|
||||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
public <T> T convert(Class<T> aClass, Object o) {
|
||||
|
|
@ -116,7 +44,7 @@ public class CommonConfiguration {
|
|||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
public <T> T convert(Class<T> type, Object value) {
|
||||
return (T)DataSize.parse(String.valueOf(value));
|
||||
return (T) DataSize.parse(String.valueOf(value));
|
||||
}
|
||||
}, DataSize.class);
|
||||
|
||||
|
|
@ -134,154 +62,6 @@ public class CommonConfiguration {
|
|||
}
|
||||
}, Interval.class);
|
||||
|
||||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
public <T> T convert(Class<T> type, Object value) {
|
||||
return (T) TimeUtils.parseUnit(String.valueOf(value));
|
||||
}
|
||||
}, ChronoUnit.class);
|
||||
|
||||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
public <T> T convert(Class<T> type, Object value) {
|
||||
|
||||
return (T)((Long)CastUtils.castNumber(value).longValue());
|
||||
}
|
||||
}, long.class);
|
||||
|
||||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
public <T> T convert(Class<T> type, Object value) {
|
||||
|
||||
return (T)((Long) CastUtils.castNumber(value).longValue());
|
||||
}
|
||||
}, Long.class);
|
||||
|
||||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
@Generated
|
||||
public <T> T convert(Class<T> type, Object value) {
|
||||
|
||||
if (value instanceof String) {
|
||||
return (T) DefaultItemDefine.builder()
|
||||
.value(String.valueOf(value))
|
||||
.build();
|
||||
}
|
||||
|
||||
return (T) FastBeanCopier.copy(value, new DefaultItemDefine());
|
||||
|
||||
}
|
||||
}, EnumDict.class);
|
||||
|
||||
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
|
||||
@Override
|
||||
@Generated
|
||||
public <T> T convert(Class<T> type, Object value) {
|
||||
if (value instanceof Map) {
|
||||
Map<String, Object> map = ((Map) value);
|
||||
String typeId = (String) map.get("type");
|
||||
if (StringUtils.isEmpty(typeId)) {
|
||||
return null;
|
||||
}
|
||||
return (T) JetLinksDataTypeCodecs.decode(DataTypes.lookup(typeId).get(), map);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
}, DataType.class);
|
||||
|
||||
//捕获jvm错误,防止Flux被挂起
|
||||
Hooks.onOperatorError((err, val) -> {
|
||||
if (Exceptions.isJvmFatal(err)) {
|
||||
return new JvmErrorException(err);
|
||||
}
|
||||
return err;
|
||||
});
|
||||
Hooks.onNextError((err, val) -> {
|
||||
if (Exceptions.isJvmFatal(err)) {
|
||||
return new JvmErrorException(err);
|
||||
}
|
||||
return err;
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ApplicationContextAware staticBeanRegister() {
|
||||
|
||||
return ctx -> {
|
||||
ctx.getBeanProvider(Feature.class)
|
||||
.forEach(DefaultReactorQLMetadata::addGlobal);
|
||||
|
||||
ctx.getBeanProvider(CommandSupportManagerProvider.class)
|
||||
.forEach(CommandSupportManagerProviders::register);
|
||||
|
||||
ctx.getBeanProvider(TermTypeSupport.class)
|
||||
.forEach(TermTypes::register);
|
||||
|
||||
ctx.getBeanProvider(FieldTypeProvider.class)
|
||||
.forEach(provider -> FieldTypeProvider.supports.register(provider.getProvider(), provider));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
|
||||
return builder->{
|
||||
builder.deserializerByType(DataType.class, new DataTypeJSONDeserializer());
|
||||
builder.deserializerByType(Date.class,new SmartDateDeserializer());
|
||||
builder.deserializerByType(EnumDict.class, new DictionaryJsonDeserializer());
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConfigManager configManager(ObjectProvider<ConfigScopeCustomizer> configScopeCustomizers,
|
||||
ReactiveRepository<ConfigEntity, String> repository,
|
||||
ReactiveCacheManager cacheManager) {
|
||||
|
||||
SimpleConfigManager configManager = new SimpleConfigManager(repository,cacheManager);
|
||||
for (ConfigScopeCustomizer customizer : configScopeCustomizers) {
|
||||
customizer.custom(configManager);
|
||||
}
|
||||
return configManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PermissionResourceProvider permissionResourceProvider(){
|
||||
return new PermissionResourceProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TypeScriptDeclareResourceProvider typeScriptDeclareResourceProvider() {
|
||||
return new TypeScriptDeclareResourceProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceManager resourceManager(ObjectProvider<ResourceProvider> providers) {
|
||||
DefaultResourceManager manager = new DefaultResourceManager();
|
||||
providers.forEach(manager::addProvider);
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataReferenceManager dataReferenceManager(ObjectProvider<DataReferenceProvider> provider) {
|
||||
DefaultDataReferenceManager referenceManager = new DefaultDataReferenceManager();
|
||||
|
||||
provider.forEach(referenceManager::addStrategy);
|
||||
|
||||
return referenceManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CommandServiceEndpointRegister commandServiceEndpointRegister() {
|
||||
return new CommandServiceEndpointRegister();
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(ReactiveRedisOperations.class)
|
||||
static class DefaultUserBindServiceConfiguration {
|
||||
@Bean
|
||||
public DefaultUserBindService defaultUserBindService(ReactiveRedisOperations<Object, Object> redis) {
|
||||
return new DefaultUserBindService(redis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.configuration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import org.apache.commons.beanutils.BeanUtilsBean;
|
||||
import org.jetlinks.core.metadata.DataType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangji 2025/1/23
|
||||
* @since 2.3
|
||||
*/
|
||||
public class DataTypeJSONDeserializer extends JsonDeserializer<DataType> {
|
||||
@Override
|
||||
public DataType deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
|
||||
Map<String,Object> map= ctxt.readValue(parser, Map.class);
|
||||
|
||||
return (DataType) BeanUtilsBean.getInstance().getConvertUtils().convert(map, DataType.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.configuration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import lombok.SneakyThrows;
|
||||
import org.jetlinks.community.utils.TimeUtils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 时间反序列化配置
|
||||
*
|
||||
* @author zhouhao
|
||||
*/
|
||||
public class SmartDateDeserializer extends JsonDeserializer<Date> {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Date deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
if (p.hasToken(JsonToken.VALUE_STRING)) {
|
||||
String str = p.getText().trim();
|
||||
if (str.length() == 0) {
|
||||
return (Date) getEmptyValue(ctxt);
|
||||
}
|
||||
return TimeUtils.parseDate(str);
|
||||
}
|
||||
if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
|
||||
long ts = p.getLongValue();
|
||||
return new Date(ts);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.configuration;
|
||||
|
||||
import org.jetlinks.community.resource.ui.UiMenuResourceProvider;
|
||||
import org.jetlinks.community.resource.ui.UiResourceProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "jetlinks.ui", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class UiResourceConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
public UiResourceProvider uiResourceProvider() {
|
||||
return new UiResourceProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UiMenuResourceProvider uiMenuResourceProvider() {
|
||||
return new UiMenuResourceProvider();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author bestfeng
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class DatabaseDictionaryManager implements DictionaryManager, CommandLineRunner{
|
||||
|
||||
private final DefaultDictionaryItemService dictionaryItemService;
|
||||
|
||||
private final Map<String, Map<String, DictionaryItemEntity>> itemStore = new ConcurrentHashMap<>();
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<EnumDict<?>> getItems(@Nonnull String dictId) {
|
||||
Map<String, DictionaryItemEntity> itemEntityMap = itemStore.get(dictId);
|
||||
if (MapUtils.isEmpty(itemEntityMap)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return new ArrayList<>(itemEntityMap.values());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Optional<EnumDict<?>> getItem(@Nonnull String dictId, @Nonnull String itemId) {
|
||||
Map<String, DictionaryItemEntity> itemEntityMap = itemStore.get(dictId);
|
||||
if (itemEntityMap == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(itemEntityMap.get(itemId));
|
||||
}
|
||||
|
||||
|
||||
public void registerItems(List<DictionaryItemEntity> items) {
|
||||
items.forEach(this::registerItem);
|
||||
}
|
||||
|
||||
|
||||
public void removeItems(List<DictionaryItemEntity> items) {
|
||||
items.forEach(this::removeItem);
|
||||
}
|
||||
|
||||
|
||||
public void removeItem(DictionaryItemEntity item) {
|
||||
if (item == null || item.getDictId() == null || item.getId() == null) {
|
||||
return;
|
||||
}
|
||||
itemStore.compute(item.getDictId(), (k, v) -> {
|
||||
if (v != null) {
|
||||
v.remove(item.getId());
|
||||
if (!v.isEmpty()) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void registerItem(DictionaryItemEntity item) {
|
||||
if (item == null || item.getDictId() == null) {
|
||||
return;
|
||||
}
|
||||
itemStore
|
||||
.computeIfAbsent(item.getDictId(), k -> new ConcurrentHashMap<>())
|
||||
.put(item.getId(), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
dictionaryItemService
|
||||
.createQuery()
|
||||
.fetch()
|
||||
.doOnNext(this::registerItem)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 动态数据字典工具类
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.1
|
||||
*/
|
||||
public class Dictionaries {
|
||||
|
||||
static DictionaryManager HOLDER = null;
|
||||
|
||||
static void setup(DictionaryManager manager) {
|
||||
HOLDER = manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典的所有选型
|
||||
*
|
||||
* @param dictId 字典ID
|
||||
* @return 字典值
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<EnumDict<?>> getItems(@Nonnull String dictId) {
|
||||
return HOLDER == null ? Collections.emptyList() : HOLDER.getItems(dictId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据掩码获取枚举选项,通常用于多选时获取选项.
|
||||
*
|
||||
* @param dictId 枚举ID
|
||||
* @param mask 掩码
|
||||
* @return 选项
|
||||
* @see Dictionaries#toMask(Collection)
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<EnumDict<?>> getItems(@Nonnull String dictId, long mask) {
|
||||
if (HOLDER == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return HOLDER
|
||||
.getItems(dictId)
|
||||
.stream()
|
||||
.filter(item -> item.in(mask))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找枚举选项
|
||||
*
|
||||
* @param dictId 枚举ID
|
||||
* @param value 选项值
|
||||
* @return 选项
|
||||
*/
|
||||
public static Optional<EnumDict<?>> findItem(@Nonnull String dictId, Object value) {
|
||||
return getItems(dictId)
|
||||
.stream()
|
||||
.filter(item -> item.eq(value))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段选型
|
||||
*
|
||||
* @param dictId 字典ID
|
||||
* @param itemId 选项ID
|
||||
* @return 选项值
|
||||
*/
|
||||
@Nonnull
|
||||
public static Optional<EnumDict<?>> getItem(@Nonnull String dictId,
|
||||
@Nonnull String itemId) {
|
||||
return HOLDER == null ? Optional.empty() : HOLDER.getItem(dictId, itemId);
|
||||
}
|
||||
|
||||
|
||||
public static long toMask(Collection<EnumDict<?>> items) {
|
||||
long value = 0L;
|
||||
for (EnumDict<?> t1 : items) {
|
||||
value |= t1.getMask();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.Codec;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 定义字段是一个数据字典,和枚举的使用方式类似.
|
||||
* <p>
|
||||
* 区别是数据的值通过{@link Dictionaries}进行获取.
|
||||
*
|
||||
* <pre>{@code
|
||||
* public class MyEntity{
|
||||
*
|
||||
* //数据库存储的是枚举的值
|
||||
* @Column(length=32)
|
||||
* @Dictionary("my_status")
|
||||
* @ColumnType(javaType=String.class)
|
||||
* private EnumDict<String> status;
|
||||
*
|
||||
* @Column
|
||||
* @Dictionary("my_types")
|
||||
* //使用long来存储数据,表示使用字段的序号来进行mask运算进行存储.
|
||||
* @ColumnType(javaType=Long.class,jdbcType=JDBCType.BIGINT)
|
||||
* private EnumDict<String>[] types;
|
||||
* }
|
||||
* }</pre>
|
||||
* <b>⚠️注意</b>
|
||||
* <ul>
|
||||
* <li>
|
||||
* 字段类型只支持{@code EnumDict<String>},{@code EnumDict<String>[]},{@code List<EnumDict<String>>}
|
||||
* </li>
|
||||
* <li>
|
||||
* 多选时建议使用位掩码来存储: {@code @ColumnType(javaType=Long.class,jdbcType=JDBCType.BIGINT) },便于查询.
|
||||
* </li>
|
||||
* <li>使用位掩码存储字典值时,基于{@link EnumDict#ordinal()}进行计算,因此字段选项数量不能超过64个,修改字典时,请注意序号值变化。</li>
|
||||
* <li>模块需要引入依赖:<pre>{@code
|
||||
* <dependency>
|
||||
* <groupId>org.hswebframework.web</groupId>
|
||||
* <artifactId>hsweb-system-dictionary</artifactId>
|
||||
* </dependency>
|
||||
* }</pre></li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @author zhouhao
|
||||
* @see EnumDict#getValue()
|
||||
* @see EnumDict#getMask()
|
||||
* @see Dictionaries
|
||||
* @see Dictionaries#toMask(Collection)
|
||||
* @see DictionaryManager
|
||||
* @since 2.2
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Codec
|
||||
public @interface Dictionary {
|
||||
|
||||
/**
|
||||
* 数据字典ID
|
||||
*
|
||||
* @return 数据字典ID
|
||||
* @see Dictionaries#getItem(String, String)
|
||||
*/
|
||||
String value();
|
||||
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
|
||||
import org.hswebframework.ezorm.rdb.metadata.RDBTableMetadata;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.term.EnumFragmentBuilder;
|
||||
import org.hswebframework.ezorm.rdb.operator.builder.fragments.term.EnumInFragmentBuilder;
|
||||
import org.hswebframework.web.crud.configuration.TableMetadataCustomizer;
|
||||
import org.jetlinks.community.form.type.EnumFieldType;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.JDBCType;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DictionaryColumnCustomizer implements TableMetadataCustomizer {
|
||||
@Override
|
||||
public void customColumn(Class<?> entityType,
|
||||
PropertyDescriptor descriptor,
|
||||
Field field,
|
||||
Set<Annotation> annotations,
|
||||
RDBColumnMetadata column) {
|
||||
Dictionary dictionary = annotations
|
||||
.stream()
|
||||
.filter(Dictionary.class::isInstance)
|
||||
.findFirst()
|
||||
.map(Dictionary.class::cast)
|
||||
.orElse(null);
|
||||
if (dictionary != null) {
|
||||
Class<?> type = field.getType();
|
||||
|
||||
JDBCType jdbcType = (JDBCType) column.getType().getSqlType();
|
||||
EnumFieldType codec = new EnumFieldType(
|
||||
type.isArray() || List.class.isAssignableFrom(type),
|
||||
dictionary.value(),
|
||||
jdbcType)
|
||||
.withArray(type.isArray())
|
||||
.withFieldValueConverter(e -> e);
|
||||
|
||||
column.setValueCodec(codec);
|
||||
if (codec.isToMask()) {
|
||||
column.addFeature(EnumFragmentBuilder.eq);
|
||||
column.addFeature(EnumFragmentBuilder.not);
|
||||
|
||||
column.addFeature(EnumInFragmentBuilder.of(column.getDialect()));
|
||||
column.addFeature(EnumInFragmentBuilder.ofNot(column.getDialect()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customTable(Class<?> entityType, RDBTableMetadata table) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import org.hswebframework.web.crud.events.EntityEventListenerCustomizer;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryService;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@AutoConfiguration
|
||||
public class DictionaryConfiguration {
|
||||
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(DefaultDictionaryItemService.class)
|
||||
//@ConditionalOnBean(DefaultDictionaryItemService.class)
|
||||
public static class DictionaryManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
public EntityEventListenerCustomizer dictionaryEntityEventListenerCustomizer() {
|
||||
return configure -> {
|
||||
configure.enable(DictionaryItemEntity.class);
|
||||
configure.enable(DictionaryEntity.class);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DictionaryEventHandler dictionaryEventHandler(DefaultDictionaryItemService service) {
|
||||
return new DictionaryEventHandler(service);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DatabaseDictionaryManager defaultDictionaryManager(DefaultDictionaryItemService service) {
|
||||
DatabaseDictionaryManager dictionaryManager = new DatabaseDictionaryManager(service);
|
||||
Dictionaries.setup(dictionaryManager);
|
||||
return dictionaryManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DictionaryColumnCustomizer dictionaryColumnCustomizer() {
|
||||
return new DictionaryColumnCustomizer();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "jetlinks.dict")
|
||||
public DictionaryInitManager dictionaryInitManager(ObjectProvider<DictionaryInitInfo> initInfo,
|
||||
DefaultDictionaryService defaultDictionaryService,
|
||||
DefaultDictionaryItemService itemService) {
|
||||
return new DictionaryInitManager(initInfo, defaultDictionaryService, itemService);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
public interface DictionaryConstants {
|
||||
|
||||
/**
|
||||
* 系统分类标识
|
||||
*/
|
||||
String CLASSIFIED_SYSTEM = "system";
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hswebframework.web.crud.events.*;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryService;
|
||||
import org.hswebframework.web.exception.BusinessException;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author bestfeng
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class DictionaryEventHandler {
|
||||
|
||||
|
||||
private final DefaultDictionaryItemService itemService;
|
||||
|
||||
@EventListener
|
||||
public void handleDictionaryCreated(EntityCreatedEvent<DictionaryEntity> event) {
|
||||
event.async(
|
||||
Flux.fromIterable(event.getEntity())
|
||||
.flatMap(dictionary -> {
|
||||
if (!CollectionUtils.isEmpty(dictionary.getItems())) {
|
||||
return Flux
|
||||
.fromIterable(dictionary.getItems())
|
||||
.doOnNext(item -> item.setDictId(dictionary.getId()))
|
||||
.as(itemService::save);
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleDictionarySaved(EntitySavedEvent<DictionaryEntity> event) {
|
||||
event.async(
|
||||
Flux.fromIterable(event.getEntity())
|
||||
.flatMap(dictionary -> {
|
||||
if (!CollectionUtils.isEmpty(dictionary.getItems())) {
|
||||
return Flux
|
||||
.fromIterable(dictionary.getItems())
|
||||
.doOnNext(item -> item.setDictId(dictionary.getId()))
|
||||
.as(itemService::save);
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleDictionaryDeleted(EntityDeletedEvent<DictionaryEntity> event) {
|
||||
event.async(
|
||||
Flux.fromIterable(event.getEntity())
|
||||
.map(DictionaryEntity::getId)
|
||||
.collectList()
|
||||
.flatMap(dictionary -> itemService
|
||||
.createDelete()
|
||||
.where()
|
||||
.in(DictionaryItemEntity::getDictId, dictionary)
|
||||
.execute()
|
||||
.then())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听字典删除前事件,阻止删除分类标识为系统的字典
|
||||
*
|
||||
* @param event 字典删除前事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleDictionaryBeforeDelete(EntityBeforeDeleteEvent<DictionaryEntity> event) {
|
||||
event.async(
|
||||
Flux.fromIterable(event.getEntity())
|
||||
.any(dictionary ->
|
||||
StringUtils.equals(dictionary.getClassified(), DictionaryConstants.CLASSIFIED_SYSTEM))
|
||||
.flatMap(any -> {
|
||||
if (any) {
|
||||
return Mono.error(() -> new BusinessException("error.system_dictionary_can_not_delete"));
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author gyl
|
||||
* @since 2.2
|
||||
*/
|
||||
public interface DictionaryInitInfo {
|
||||
Collection<DictionaryEntity> getDict();
|
||||
|
||||
default Flux<DictionaryEntity> getDictAsync() {
|
||||
if (CollectionUtils.isEmpty(getDict())) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return Flux.fromIterable(getDict());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.hswebframework.web.crud.events.EntityEventHelper;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
|
||||
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
|
||||
import org.hswebframework.web.dictionary.service.DefaultDictionaryService;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author gyl
|
||||
* @since 2.2
|
||||
*/
|
||||
@Slf4j
|
||||
public class DictionaryInitManager implements CommandLineRunner {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<DictionaryEntity> inits = new ArrayList<>();
|
||||
|
||||
public final ObjectProvider<DictionaryInitInfo> initInfo;
|
||||
|
||||
private final DefaultDictionaryService defaultDictionaryService;
|
||||
|
||||
private final DefaultDictionaryItemService itemService;
|
||||
|
||||
public DictionaryInitManager(ObjectProvider<DictionaryInitInfo> initInfo, DefaultDictionaryService defaultDictionaryService, DefaultDictionaryItemService itemService) {
|
||||
this.initInfo = initInfo;
|
||||
this.defaultDictionaryService = defaultDictionaryService;
|
||||
this.itemService = itemService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
Flux
|
||||
.merge(
|
||||
Flux.fromIterable(inits),
|
||||
Flux
|
||||
.fromIterable(initInfo)
|
||||
.flatMap(DictionaryInitInfo::getDictAsync)
|
||||
)
|
||||
.buffer(200)
|
||||
.filter(CollectionUtils::isNotEmpty)
|
||||
.flatMap(collection -> {
|
||||
List<DictionaryItemEntity> items = generateItems(collection);
|
||||
return defaultDictionaryService
|
||||
.save(collection)
|
||||
.mergeWith(itemService.save(items));
|
||||
})
|
||||
.as(EntityEventHelper::setDoNotFireEvent)
|
||||
.subscribe(ignore -> {
|
||||
},
|
||||
err -> log.error("init dict error", err));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public List<DictionaryItemEntity> generateItems(List<DictionaryEntity> dictionaryList) {
|
||||
List<DictionaryItemEntity> items = new ArrayList<>();
|
||||
for (DictionaryEntity dictionary : dictionaryList) {
|
||||
if (!CollectionUtils.isEmpty(dictionary.getItems())) {
|
||||
for (DictionaryItemEntity item : dictionary.getItems()) {
|
||||
item.setDictId(dictionary.getId());
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
import org.hswebframework.web.dict.defaults.DefaultItemDefine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class DictionaryJsonDeserializer extends JsonDeserializer<EnumDict<?>> {
|
||||
@Override
|
||||
public EnumDict<?> deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JacksonException {
|
||||
if (jsonParser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
|
||||
DefaultItemDefine defaultItemDefine = new DefaultItemDefine();
|
||||
defaultItemDefine.setOrdinal(jsonParser.getIntValue());
|
||||
return defaultItemDefine;
|
||||
}
|
||||
if (jsonParser.hasToken(JsonToken.VALUE_STRING)) {
|
||||
String str = jsonParser.getText().trim();
|
||||
if (!str.isEmpty()) {
|
||||
DefaultItemDefine defaultItemDefine = new DefaultItemDefine();
|
||||
defaultItemDefine.setValue(str);
|
||||
return defaultItemDefine;
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonParser.hasToken(JsonToken.START_OBJECT)) {
|
||||
Map<?, ?> map = ctxt.readValue(jsonParser, Map.class);
|
||||
if (map != null) {
|
||||
return FastBeanCopier.copy(map, new DefaultItemDefine());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright 2025 JetLinks https://www.jetlinks.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetlinks.community.dictionary;
|
||||
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 数据字典管理器,用于获取数据字典的枚举值
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface DictionaryManager {
|
||||
|
||||
/**
|
||||
* 获取字典的所有选项
|
||||
*
|
||||
* @param dictId 字典ID
|
||||
* @return 字典值
|
||||
*/
|
||||
@Nonnull
|
||||
List<EnumDict<?>> getItems(@Nonnull String dictId);
|
||||
|
||||
/**
|
||||
* 获取字段选项
|
||||
*
|
||||
* @param dictId 字典ID
|
||||
* @param itemId 选项ID
|
||||
* @return 选项值
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<EnumDict<?>> getItem(@Nonnull String dictId,
|
||||
@Nonnull String itemId);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue