parent
727232a743
commit
091cc9ca70
63 changed files with 509 additions and 2795 deletions
@ -0,0 +1,8 @@ |
|||||||
|
# Ignore all files in the `dist` directory |
||||||
|
dist/ |
||||||
|
|
||||||
|
# Ignore all `.log` files |
||||||
|
*.log |
||||||
|
|
||||||
|
# Ignore specific file `config.json` |
||||||
|
config.json |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
|
||||||
|
# .NET Development Rules |
||||||
|
|
||||||
|
You are a senior .NET backend developer and an expert in C#, ASP.NET Core, and Entity Framework Core. |
||||||
|
|
||||||
|
## Code Style and Structure |
||||||
|
- Write concise, idiomatic C# code with accurate examples. |
||||||
|
- Follow .NET and ASP.NET Core conventions and best practices. |
||||||
|
- Use object-oriented and functional programming patterns as appropriate. |
||||||
|
- Prefer LINQ and lambda expressions for collection operations. |
||||||
|
- Use descriptive variable and method names (e.g., 'IsUserSignedIn', 'CalculateTotal'). |
||||||
|
- Structure files according to .NET conventions (Controllers, Models, Services, etc.). |
||||||
|
|
||||||
|
## Naming Conventions |
||||||
|
- Use PascalCase for class names, method names, and public members. |
||||||
|
- Use camelCase for local variables and private fields. |
||||||
|
- Use UPPERCASE for constants. |
||||||
|
- Prefix interface names with "I" (e.g., 'IUserService'). |
||||||
|
|
||||||
|
## C# and .NET Usage |
||||||
|
- Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment). |
||||||
|
- Leverage built-in ASP.NET Core features and middleware. |
||||||
|
- Use Entity Framework Core effectively for database operations. |
||||||
|
|
||||||
|
## Syntax and Formatting |
||||||
|
- Follow the C# Coding Conventions (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) |
||||||
|
- Use C#'s expressive syntax (e.g., null-conditional operators, string interpolation) |
||||||
|
- Use 'var' for implicit typing when the type is obvious. |
||||||
|
|
||||||
|
## Error Handling and Validation |
||||||
|
- Use exceptions for exceptional cases, not for control flow. |
||||||
|
- Implement proper error logging using built-in .NET logging or a third-party logger. |
||||||
|
- Use Data Annotations or Fluent Validation for model validation. |
||||||
|
- Implement global exception handling middleware. |
||||||
|
- Return appropriate HTTP status codes and consistent error responses. |
||||||
|
|
||||||
|
## API Design |
||||||
|
- Follow RESTful API design principles. |
||||||
|
- Use attribute routing in controllers. |
||||||
|
- Implement versioning for your API. |
||||||
|
- Use action filters for cross-cutting concerns. |
||||||
|
|
||||||
|
## Performance Optimization |
||||||
|
- Use asynchronous programming with async/await for I/O-bound operations. |
||||||
|
- Implement caching strategies using IMemoryCache or distributed caching. |
||||||
|
- Use efficient LINQ queries and avoid N+1 query problems. |
||||||
|
- Implement pagination for large data sets. |
||||||
|
|
||||||
|
## Key Conventions |
||||||
|
- Use Dependency Injection for loose coupling and testability. |
||||||
|
- Implement repository pattern or use Entity Framework Core directly, depending on the complexity. |
||||||
|
- Use AutoMapper for object-to-object mapping if needed. |
||||||
|
- Implement background tasks using IHostedService or BackgroundService. |
||||||
|
|
||||||
|
## Testing |
||||||
|
- Write unit tests using xUnit, NUnit, or MSTest. |
||||||
|
- Use Moq or NSubstitute for mocking dependencies. |
||||||
|
- Implement integration tests for API endpoints. |
||||||
|
|
||||||
|
## Security |
||||||
|
- Use Authentication and Authorization middleware. |
||||||
|
- Implement JWT authentication for stateless API authentication. |
||||||
|
- Use HTTPS and enforce SSL. |
||||||
|
- Implement proper CORS policies. |
||||||
|
|
||||||
|
## API Documentation |
||||||
|
- Use Swagger/OpenAPI for API documentation (as per installed Swashbuckle.AspNetCore package). |
||||||
|
- Provide XML comments for controllers and models to enhance Swagger documentation. |
||||||
|
|
||||||
|
Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components. |
||||||
@ -1,37 +1,230 @@ |
|||||||
# Rules in this file were initially inferred by Visual Studio IntelliCode from the NetEscapades.AspNetCore.SecurityHeaders codebase based on best match to current usage at 16/11/2018 |
# Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\workspaces\opensource\uuac codebase based on best match to current usage at 1/18/2022 |
||||||
|
# There already existed an .editorconfig file in this directory. Copy rules from this .editorconfig.inferred file to the existing .editorconfig file as desired to have them take effect at this location. |
||||||
# You can modify the rules from these initially generated values to suit your own policies |
# You can modify the rules from these initially generated values to suit your own policies |
||||||
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference |
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference |
||||||
[*.cs] |
[*.cs] |
||||||
|
|
||||||
|
file_header_template = Copyright (c) Xuanye Wong. All rights reserved.\nLicensed under MIT license |
||||||
|
dotnet_diagnostic.IDE0073.severity = warning |
||||||
#Core editorconfig formatting - indentation |
#Core editorconfig formatting - indentation |
||||||
|
|
||||||
#use soft tabs (spaces) for indentation |
#use soft tabs (spaces) for indentation |
||||||
indent_style = space |
indent_style = space |
||||||
|
indent_size = 4 |
||||||
|
#Formatting - new line options |
||||||
|
|
||||||
#Formatting - indentation options |
#require members of object intializers to be on separate lines |
||||||
|
csharp_new_line_before_members_in_object_initializers = true |
||||||
|
#require braces to be on a new line for lambdas, types, control_blocks, and methods (also known as "Allman" style) |
||||||
|
csharp_new_line_before_open_brace = all |
||||||
|
|
||||||
#indent switch case contents. |
#Formatting - organize using options |
||||||
csharp_indent_case_contents = true |
|
||||||
#indent switch labels |
|
||||||
csharp_indent_switch_labels = true |
|
||||||
|
|
||||||
#Formatting - new line options |
#do not place System.* using directives before other using directives |
||||||
|
dotnet_sort_system_directives_first = false |
||||||
|
|
||||||
#require braces to be on a new line for types, object_collection, methods, control_blocks, and lambdas (also known as "Allman" style) |
#Formatting - spacing options |
||||||
csharp_new_line_before_open_brace = types, object_collection, methods, control_blocks, lambdas |
|
||||||
|
|
||||||
#Formatting - organize using options |
#require NO space between a cast and the value |
||||||
|
csharp_space_after_cast = false |
||||||
|
#require a space before the colon for bases or interfaces in a type declaration |
||||||
|
csharp_space_after_colon_in_inheritance_clause = true |
||||||
|
#require a space after a keyword in a control flow statement such as a for loop |
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true |
||||||
|
#require a space before the colon for bases or interfaces in a type declaration |
||||||
|
csharp_space_before_colon_in_inheritance_clause = true |
||||||
|
#remove space within empty argument list parentheses |
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false |
||||||
|
#remove space between method call name and opening parenthesis |
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false |
||||||
|
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call |
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false |
||||||
|
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. |
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false |
||||||
|
|
||||||
|
#Formatting - wrapping options |
||||||
|
|
||||||
|
#leave code block on separate lines |
||||||
|
csharp_preserve_single_line_blocks = true |
||||||
|
|
||||||
|
#Style - Code block preferences |
||||||
|
|
||||||
|
#prefer curly braces even for one line of code |
||||||
|
csharp_prefer_braces = when_multiline:suggestion |
||||||
|
|
||||||
|
#Style - expression bodied member options |
||||||
|
|
||||||
|
#prefer block bodies for accessors |
||||||
|
csharp_style_expression_bodied_accessors = false:suggestion |
||||||
|
#prefer block bodies for constructors |
||||||
|
csharp_style_expression_bodied_constructors = false:suggestion |
||||||
|
#prefer block bodies for methods |
||||||
|
csharp_style_expression_bodied_methods = false:suggestion |
||||||
|
#prefer block bodies for properties |
||||||
|
csharp_style_expression_bodied_properties = true:suggestion |
||||||
|
|
||||||
|
#Style - Expression-level preferences |
||||||
|
|
||||||
|
#prefer objects to be initialized using object initializers when possible |
||||||
|
dotnet_style_object_initializer = true:suggestion |
||||||
|
|
||||||
|
#Style - implicit and explicit types |
||||||
|
|
||||||
|
#prefer var over explicit type in all cases, unless overridden by another code style rule |
||||||
|
csharp_style_var_elsewhere = true:suggestion |
||||||
|
#prefer var when the type is already mentioned on the right-hand side of a declaration expression |
||||||
|
csharp_style_var_when_type_is_apparent = true:suggestion |
||||||
|
|
||||||
|
#Style - language keyword and framework type options |
||||||
|
|
||||||
|
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them |
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion |
||||||
|
|
||||||
|
#Style - modifier options |
||||||
|
|
||||||
|
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. |
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion |
||||||
|
|
||||||
#sort System.* using directives alphabetically, and place them before other usings |
#Style - Modifier preferences |
||||||
dotnet_sort_system_directives_first = true |
|
||||||
|
|
||||||
... |
#when this rule is set to a list of modifiers, prefer the specified ordering. |
||||||
|
csharp_preferred_modifier_order = public,private,static,readonly:suggestion |
||||||
|
|
||||||
#Style - qualification options |
#Style - qualification options |
||||||
|
|
||||||
#prefer fields not to be prefaced with this. or Me. in Visual Basic |
#prefer fields not to be prefaced with this. or Me. in Visual Basic |
||||||
dotnet_style_qualification_for_field = false:suggestion |
dotnet_style_qualification_for_field = false:suggestion |
||||||
#prefer methods not to be prefaced with this. or Me. in Visual Basic |
|
||||||
dotnet_style_qualification_for_method = false:suggestion |
|
||||||
#prefer properties not to be prefaced with this. or Me. in Visual Basic |
#prefer properties not to be prefaced with this. or Me. in Visual Basic |
||||||
dotnet_style_qualification_for_property = false:suggestion |
dotnet_style_qualification_for_property = false:suggestion |
||||||
|
csharp_indent_labels = one_less_than_current |
||||||
|
csharp_using_directive_placement = outside_namespace:silent |
||||||
|
csharp_prefer_simple_using_statement = true:suggestion |
||||||
|
csharp_style_namespace_declarations = block_scoped:silent |
||||||
|
csharp_style_prefer_method_group_conversion = true:silent |
||||||
|
csharp_style_prefer_top_level_statements = true:silent |
||||||
|
csharp_style_expression_bodied_operators = false:silent |
||||||
|
csharp_style_expression_bodied_indexers = true:silent |
||||||
|
csharp_style_expression_bodied_lambdas = true:silent |
||||||
|
csharp_style_expression_bodied_local_functions = false:silent |
||||||
|
csharp_style_throw_expression = true:suggestion |
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion |
||||||
|
csharp_prefer_simple_default_expression = true:suggestion |
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion |
||||||
|
csharp_style_prefer_index_operator = true:suggestion |
||||||
|
csharp_style_prefer_range_operator = true:suggestion |
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion |
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion |
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion |
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion |
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion |
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion |
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent |
||||||
|
csharp_prefer_static_local_function = true:suggestion |
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent |
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent |
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent |
||||||
|
csharp_style_conditional_delegate_call = true:suggestion |
||||||
|
csharp_style_prefer_switch_expression = true:suggestion |
||||||
|
csharp_style_prefer_pattern_matching = true:silent |
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion |
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion |
||||||
|
csharp_style_prefer_extended_property_pattern = true:suggestion |
||||||
|
csharp_style_prefer_not_pattern = true:suggestion |
||||||
|
csharp_style_var_for_built_in_types = false:silent |
||||||
|
csharp_space_around_binary_operators = before_and_after |
||||||
|
csharp_style_prefer_readonly_struct = true:suggestion |
||||||
|
|
||||||
|
[*.{cs,vb}] |
||||||
|
#### Naming styles #### |
||||||
|
|
||||||
|
# Naming rules |
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion |
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface |
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i |
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion |
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types |
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case |
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion |
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members |
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case |
||||||
|
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be__camelcasename.severity = suggestion |
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be__camelcasename.symbols = private_or_internal_field |
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be__camelcasename.style = _camelcasename |
||||||
|
|
||||||
|
# Symbol specifications |
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface |
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
||||||
|
dotnet_naming_symbols.interface.required_modifiers = |
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum |
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
||||||
|
dotnet_naming_symbols.types.required_modifiers = |
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method |
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers = |
||||||
|
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field |
||||||
|
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected |
||||||
|
dotnet_naming_symbols.private_or_internal_field.required_modifiers = |
||||||
|
|
||||||
|
# Naming styles |
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I |
||||||
|
dotnet_naming_style.begins_with_i.required_suffix = |
||||||
|
dotnet_naming_style.begins_with_i.word_separator = |
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case |
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix = |
||||||
|
dotnet_naming_style.pascal_case.required_suffix = |
||||||
|
dotnet_naming_style.pascal_case.word_separator = |
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case |
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix = |
||||||
|
dotnet_naming_style.pascal_case.required_suffix = |
||||||
|
dotnet_naming_style.pascal_case.word_separator = |
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case |
||||||
|
|
||||||
|
dotnet_naming_style._camelcasename.required_prefix = _ |
||||||
|
dotnet_naming_style._camelcasename.required_suffix = |
||||||
|
dotnet_naming_style._camelcasename.word_separator = |
||||||
|
dotnet_naming_style._camelcasename.capitalization = camel_case |
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line |
||||||
|
tab_width = 4 |
||||||
|
indent_size = 4 |
||||||
|
end_of_line = crlf |
||||||
|
dotnet_style_coalesce_expression = true:suggestion |
||||||
|
dotnet_style_null_propagation = true:suggestion |
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion |
||||||
|
dotnet_style_prefer_auto_properties = true:silent |
||||||
|
dotnet_style_object_initializer = true:suggestion |
||||||
|
dotnet_style_collection_initializer = true:suggestion |
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion |
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent |
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:silent |
||||||
|
dotnet_style_explicit_tuple_names = true:suggestion |
||||||
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion |
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion |
||||||
|
dotnet_style_prefer_compound_assignment = true:suggestion |
||||||
|
dotnet_style_prefer_simplified_interpolation = true:suggestion |
||||||
|
dotnet_style_namespace_match_folder = true:suggestion |
||||||
|
dotnet_style_readonly_field = true:suggestion |
||||||
|
dotnet_style_predefined_type_for_member_access = true:silent |
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion |
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion |
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = true:silent |
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent |
||||||
|
dotnet_code_quality_unused_parameters = all:suggestion |
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent |
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent |
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent |
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent |
||||||
|
dotnet_style_qualification_for_field = false:suggestion |
||||||
|
dotnet_style_qualification_for_method = false:silent |
||||||
|
dotnet_style_qualification_for_property = false:suggestion |
||||||
|
dotnet_style_qualification_for_event = false:silent |
||||||
|
|||||||
@ -1,70 +1,70 @@ |
|||||||
# Misc folders |
|
||||||
[Bb]in/ |
|
||||||
[Oo]bj/ |
[Oo]bj/ |
||||||
[Pp]ackages/ |
[Bb]in/ |
||||||
|
TestResults/ |
||||||
# Build related |
.nuget/ |
||||||
tools/** |
_ReSharper.*/ |
||||||
!tools/packages.config |
packages/ |
||||||
|
artifacts/ |
||||||
## Ignore Visual Studio temporary files, build results, and |
PublishProfiles/ |
||||||
## files generated by popular Visual Studio add-ons. |
|
||||||
|
|
||||||
# User-specific files |
|
||||||
*.suo |
|
||||||
*.user |
*.user |
||||||
*.sln.docstates |
*.suo |
||||||
*.sln.ide/ |
*.cache |
||||||
*.userprefs |
*.docstates |
||||||
*.GhostDoc.xml |
_ReSharper.* |
||||||
|
nuget.exe |
||||||
# Build results |
*net45.csproj |
||||||
[Dd]ebug/ |
*net451.csproj |
||||||
[Rr]elease/ |
*k10.csproj |
||||||
x64/ |
|
||||||
*_i.c |
|
||||||
*_p.c |
|
||||||
*.ilk |
|
||||||
*.meta |
|
||||||
*.obj |
|
||||||
*.pch |
|
||||||
*.pdb |
|
||||||
*.pgc |
|
||||||
*.pgd |
|
||||||
*.rsp |
|
||||||
*.sbr |
|
||||||
*.tlb |
|
||||||
*.tli |
|
||||||
*.tlh |
|
||||||
*.tmp |
|
||||||
*.log |
|
||||||
*.vspscc |
|
||||||
*.vssscc |
|
||||||
.builds |
|
||||||
|
|
||||||
# Visual Studio profiler |
|
||||||
*.psess |
*.psess |
||||||
*.vsp |
*.vsp |
||||||
*.vspx |
*.pidb |
||||||
|
*.userprefs |
||||||
# ReSharper is a .NET coding add-in |
*DS_Store |
||||||
_ReSharper* |
*.ncrunchsolution |
||||||
|
*.*sdf |
||||||
# NCrunch |
*.ipch |
||||||
*.ncrunch* |
*.sln.ide |
||||||
.*crunch*.local.xml |
*.sublime-workspace |
||||||
_NCrunch_* |
node_modules/ |
||||||
|
node_modules1/ |
||||||
artifacts/ |
node_modules2/ |
||||||
# NuGet Packages Directory |
.build/ |
||||||
packages |
logs/ |
||||||
|
typings/ |
||||||
# Windows |
project.lock.json |
||||||
Thumbs.db |
classes |
||||||
|
data |
||||||
# NUnit |
temp |
||||||
TestResult.xml |
doc/site |
||||||
|
dist |
||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
# Runtime data |
||||||
|
pids |
||||||
|
*.pid |
||||||
|
*.seed |
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||||
|
lib-cov |
||||||
|
# Coverage directory used by tools like istanbul |
||||||
|
coverage |
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) |
||||||
|
.grunt |
||||||
|
# node-waf configuration |
||||||
|
.lock-wscript |
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html) |
||||||
|
build/Release |
||||||
|
# Dependency directory |
||||||
|
node_modules |
||||||
|
# Optional npm cache directory |
||||||
|
.npm |
||||||
|
# Optional REPL history |
||||||
|
.node_repl_history |
||||||
|
cache/ |
||||||
.vs/ |
.vs/ |
||||||
|
*.lnk |
||||||
|
*.Production.json |
||||||
.idea/ |
.idea/ |
||||||
|
.vscode/ |
||||||
doc/_site/ |
workspace.xml |
||||||
|
|||||||
@ -1,13 +0,0 @@ |
|||||||
{ |
|
||||||
"version": 1, |
|
||||||
"isRoot": true, |
|
||||||
"tools": { |
|
||||||
"dotnet-ef": { |
|
||||||
"version": "8.0.7", |
|
||||||
"commands": [ |
|
||||||
"dotnet-ef" |
|
||||||
], |
|
||||||
"rollForward": false |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,32 @@ |
|||||||
|
using Microsoft.AspNetCore.Mvc; |
||||||
|
|
||||||
|
namespace AspNetCoreExecutor.Controllers |
||||||
|
{ |
||||||
|
[ApiController] |
||||||
|
[Route("[controller]")]
|
||||||
|
public class WeatherForecastController : ControllerBase |
||||||
|
{ |
||||||
|
private static readonly string[] Summaries = new[] |
||||||
|
{ |
||||||
|
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" |
||||||
|
}; |
||||||
|
|
||||||
|
private readonly ILogger<WeatherForecastController> _logger; |
||||||
|
|
||||||
|
public WeatherForecastController(ILogger<WeatherForecastController> logger) |
||||||
|
{ |
||||||
|
_logger = logger; |
||||||
|
} |
||||||
|
|
||||||
|
[HttpGet(Name = "GetWeatherForecast")] |
||||||
|
public IEnumerable<WeatherForecast> Get() |
||||||
|
{ |
||||||
|
return Enumerable.Range(1, 5).Select(index => new WeatherForecast { |
||||||
|
Date = DateTime.Now.AddDays(index), |
||||||
|
TemperatureC = Random.Shared.Next(-20, 55), |
||||||
|
Summary = Summaries[Random.Shared.Next(Summaries.Length)] |
||||||
|
}) |
||||||
|
.ToArray(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,22 +0,0 @@ |
|||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace ASPNetCoreExecutor |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// 示例Job,只是写个日志 |
|
||||||
/// </summary> |
|
||||||
[JobHandler("demoJobHandler")] |
|
||||||
public class DemoJobHandler:AbstractJobHandler |
|
||||||
{ |
|
||||||
public override async Task<ReturnT> Execute(JobExecuteContext context) |
|
||||||
{ |
|
||||||
context.JobLogger.Log("receive demo job handler,parameter:{0}",context.JobParameter); |
|
||||||
context.JobLogger.Log("开始休眠10秒"); |
|
||||||
await Task.Delay(10 * 1000); |
|
||||||
context.JobLogger.Log("休眠10秒结束"); |
|
||||||
return ReturnT.SUCCESS; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
using Microsoft.AspNetCore.Builder; |
|
||||||
|
|
||||||
namespace ASPNetCoreExecutor |
|
||||||
{ |
|
||||||
public static class ApplicationBuilderExtensions |
|
||||||
{ |
|
||||||
public static IApplicationBuilder UseXxlJobExecutor(this IApplicationBuilder @this) |
|
||||||
{ |
|
||||||
return @this.UseMiddleware<XxlJobExecutorMiddleware>(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,42 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.IO; |
|
||||||
using System.Net; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core; |
|
||||||
using Microsoft.AspNetCore.Http; |
|
||||||
using Microsoft.Extensions.DependencyInjection; |
|
||||||
|
|
||||||
namespace ASPNetCoreExecutor |
|
||||||
{ |
|
||||||
public class XxlJobExecutorMiddleware |
|
||||||
{ |
|
||||||
private readonly IServiceProvider _provider; |
|
||||||
private readonly RequestDelegate _next; |
|
||||||
|
|
||||||
private readonly XxlRestfulServiceHandler _rpcService; |
|
||||||
public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next) |
|
||||||
{ |
|
||||||
this._provider = provider; |
|
||||||
this._next = next; |
|
||||||
this._rpcService = _provider.GetRequiredService<XxlRestfulServiceHandler>(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public async Task Invoke(HttpContext context) |
|
||||||
{ |
|
||||||
string contentType = context.Request.ContentType; |
|
||||||
|
|
||||||
if ("POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase) |
|
||||||
&& !string.IsNullOrEmpty(contentType) |
|
||||||
&& contentType.ToLower().StartsWith("application/json")) |
|
||||||
{ |
|
||||||
|
|
||||||
await _rpcService.HandlerAsync(context.Request,context.Response); |
|
||||||
|
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
await _next.Invoke(context); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,13 @@ |
|||||||
|
namespace AspNetCoreExecutor |
||||||
|
{ |
||||||
|
public class WeatherForecast |
||||||
|
{ |
||||||
|
public DateTime Date { get; set; } |
||||||
|
|
||||||
|
public int TemperatureC { get; set; } |
||||||
|
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); |
||||||
|
|
||||||
|
public string? Summary { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
{ |
||||||
|
"Logging": { |
||||||
|
"LogLevel": { |
||||||
|
"Default": "Information", |
||||||
|
"Microsoft.AspNetCore": "Warning" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,16 +1,9 @@ |
|||||||
{ |
{ |
||||||
"Logging": { |
"Logging": { |
||||||
"LogLevel": { |
"LogLevel": { |
||||||
"Default": "Information" |
"Default": "Information", |
||||||
|
"Microsoft.AspNetCore": "Warning" |
||||||
} |
} |
||||||
}, |
}, |
||||||
"xxlJob": { |
"AllowedHosts": "*" |
||||||
"adminAddresses": "https://jobs.xuanye.wang/xxl-job-admin", |
|
||||||
"appName": "xxl-job-executor-dotnet", |
|
||||||
"specialBindAddress": "127.0.0.1", |
|
||||||
"port": 6662, |
|
||||||
"autoRegistry": true, |
|
||||||
"accessToken": "", |
|
||||||
"logRetentionDays": 30 |
|
||||||
} |
|
||||||
} |
} |
||||||
@ -1,109 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.IO; |
|
||||||
using System.Linq; |
|
||||||
using System.Net.Http; |
|
||||||
using System.Net.Http.Headers; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Config; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using Flurl; |
|
||||||
using Flurl.Http; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using Microsoft.Extensions.Options; |
|
||||||
using Newtonsoft.Json; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public class AdminClient |
|
||||||
{ |
|
||||||
|
|
||||||
private readonly XxlJobExecutorOptions _options; |
|
||||||
private readonly ILogger<AdminClient> _logger; |
|
||||||
private List<AddressEntry> _addresses; |
|
||||||
private int _currentIndex; |
|
||||||
private static readonly string MAPPING = "/api"; |
|
||||||
public AdminClient(IOptions<XxlJobExecutorOptions> optionsAccessor |
|
||||||
, ILogger<AdminClient> logger) |
|
||||||
{ |
|
||||||
Preconditions.CheckNotNull(optionsAccessor?.Value, "XxlJobExecutorOptions"); |
|
||||||
|
|
||||||
this._options = optionsAccessor?.Value; |
|
||||||
this._logger = logger; |
|
||||||
InitAddress(); |
|
||||||
} |
|
||||||
|
|
||||||
private void InitAddress() |
|
||||||
{ |
|
||||||
this._addresses = new List<AddressEntry>(); |
|
||||||
foreach (var item in this._options.AdminAddresses.Split(';')) |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
var entry = new AddressEntry { RequestUri = item+ MAPPING }; |
|
||||||
this._addresses.Add(entry); |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
this._logger.LogError(ex, "init admin address error."); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public Task<ReturnT> Callback(List<HandleCallbackParam> callbackParamList) |
|
||||||
{ |
|
||||||
|
|
||||||
return InvokeRpcService("callback", callbackParamList); |
|
||||||
} |
|
||||||
|
|
||||||
public Task<ReturnT> Registry(RegistryParam registryParam) |
|
||||||
{ |
|
||||||
return InvokeRpcService("registry", registryParam); |
|
||||||
} |
|
||||||
|
|
||||||
public Task<ReturnT> RegistryRemove(RegistryParam registryParam) |
|
||||||
{ |
|
||||||
return InvokeRpcService("registryRemove", registryParam); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private async Task<ReturnT> InvokeRpcService(string methodName, object jsonObject) |
|
||||||
{ |
|
||||||
var triedTimes = 0; |
|
||||||
ReturnT ret = null; |
|
||||||
|
|
||||||
while (triedTimes++ < this._addresses.Count) |
|
||||||
{ |
|
||||||
|
|
||||||
var address = this._addresses[this._currentIndex]; |
|
||||||
this._currentIndex = (this._currentIndex + 1) % this._addresses.Count; |
|
||||||
if (!address.CheckAccessible()) |
|
||||||
continue; |
|
||||||
try |
|
||||||
{ |
|
||||||
|
|
||||||
var json = await address.RequestUri.AppendPathSegment(methodName) |
|
||||||
.WithHeader("XXL-JOB-ACCESS-TOKEN", this._options.AccessToken) |
|
||||||
.PostJsonAsync(jsonObject) |
|
||||||
.ReceiveString(); |
|
||||||
|
|
||||||
//.ReceiveJson<ReturnT>(); |
|
||||||
ret = JsonConvert.DeserializeObject<ReturnT>(json); |
|
||||||
address.Reset(); |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
this._logger.LogError(ex, "request admin error.{0}", ex.Message); |
|
||||||
address.SetFail(); |
|
||||||
continue; |
|
||||||
} |
|
||||||
} |
|
||||||
if(ret == null) |
|
||||||
{ |
|
||||||
ret = ReturnT.Failed("call admin fail"); |
|
||||||
} |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] |
|
||||||
public class JobHandlerAttribute:Attribute |
|
||||||
{ |
|
||||||
public JobHandlerAttribute(string name) |
|
||||||
{ |
|
||||||
Name = name; |
|
||||||
} |
|
||||||
|
|
||||||
public string Name { get; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// set Ignore |
|
||||||
/// </summary> |
|
||||||
public bool Ignore { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,65 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.IO; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Config |
|
||||||
{ |
|
||||||
public class XxlJobExecutorOptions |
|
||||||
{ |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 管理端地址,多个以;分隔 |
|
||||||
/// </summary> |
|
||||||
public string AdminAddresses { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// appName自动注册时要去管理端配置一致 |
|
||||||
/// </summary> |
|
||||||
public string AppName { get; set; } = "xxl-job-executor-dotnet"; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 绑定的特殊的URL,如果该项配置存在,则忽略SpecialBindAddress和Port |
|
||||||
/// </summary> |
|
||||||
public string SpecialBindUrl { get; set; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 自动注册时提交的地址,为空会自动获取内网地址 |
|
||||||
/// </summary> |
|
||||||
public string SpecialBindAddress { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 绑定端口 |
|
||||||
/// </summary> |
|
||||||
public int Port { get; set; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 是否自动注册 |
|
||||||
/// </summary> |
|
||||||
public bool AutoRegistry { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 认证票据 |
|
||||||
/// </summary> |
|
||||||
public string AccessToken { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 日志目录,默认为执行目录的logs子目录下,请配置绝对路径 |
|
||||||
/// </summary> |
|
||||||
public string LogPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "./logs"); |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 日志保留天数 |
|
||||||
/// </summary> |
|
||||||
public int LogRetentionDays { get; set; } = 30; |
|
||||||
|
|
||||||
|
|
||||||
public int CallBackInterval { get; set; } = 500; //回调时间间隔 500毫秒 |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,16 +0,0 @@ |
|||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.DefaultHandlers |
|
||||||
{ |
|
||||||
public abstract class AbsJobHandler:IJobHandler |
|
||||||
{ |
|
||||||
public virtual void Dispose() |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
public abstract Task<ReturnT> Execute(JobExecuteContext context); |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,66 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Net; |
|
||||||
using System.Net.Http; |
|
||||||
using System.Text.RegularExpressions; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.DefaultHandlers |
|
||||||
{ |
|
||||||
[JobHandler("simpleHttpJobHandler")] |
|
||||||
public class SimpleHttpJobHandler:AbsJobHandler |
|
||||||
{ |
|
||||||
private readonly IHttpClientFactory _httpClientFactory; |
|
||||||
|
|
||||||
private static readonly Regex UrlPattern = |
|
||||||
new Regex(@"(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); |
|
||||||
public SimpleHttpJobHandler(IHttpClientFactory httpClientFactory) |
|
||||||
{ |
|
||||||
this._httpClientFactory = httpClientFactory; |
|
||||||
} |
|
||||||
public override async Task<ReturnT> Execute(JobExecuteContext context) |
|
||||||
{ |
|
||||||
if (string.IsNullOrEmpty(context.JobParameter)) |
|
||||||
{ |
|
||||||
return ReturnT.Failed("url is empty"); |
|
||||||
} |
|
||||||
|
|
||||||
string url = context.JobParameter; |
|
||||||
|
|
||||||
if (!UrlPattern.IsMatch(url)) |
|
||||||
{ |
|
||||||
return ReturnT.Failed("url format is not valid"); |
|
||||||
} |
|
||||||
context.JobLogger.Log("Get Request Data:{0}",context.JobParameter); |
|
||||||
using (var client = this._httpClientFactory.CreateClient(Constants.DefaultHttpClientName)) |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
var response = await client.GetAsync(url); |
|
||||||
if (response == null) |
|
||||||
{ |
|
||||||
context.JobLogger.Log("call remote error,response is null"); |
|
||||||
return ReturnT.Failed("call remote error,response is null"); |
|
||||||
} |
|
||||||
|
|
||||||
if (response.StatusCode != HttpStatusCode.OK) |
|
||||||
{ |
|
||||||
context.JobLogger.Log("call remote error,response statusCode ={0}",response.StatusCode); |
|
||||||
return ReturnT.Failed("call remote error,response statusCode ="+response.StatusCode); |
|
||||||
} |
|
||||||
|
|
||||||
string body = await response.Content.ReadAsStringAsync(); |
|
||||||
context.JobLogger.Log("<br/> call remote success ,response is : <br/> {0}",body); |
|
||||||
return ReturnT.SUCCESS; |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
context.JobLogger.LogError(ex); |
|
||||||
return ReturnT.Failed(ex.Message); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,49 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Reflection; |
|
||||||
using Microsoft.Extensions.DependencyInjection; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public class DefaultJobHandlerFactory:IJobHandlerFactory |
|
||||||
{ |
|
||||||
private readonly IServiceProvider _provider; |
|
||||||
private readonly Dictionary<string, IJobHandler> handlersCache = new Dictionary<string, IJobHandler>(); |
|
||||||
public DefaultJobHandlerFactory(IServiceProvider provider) |
|
||||||
{ |
|
||||||
this._provider = provider; |
|
||||||
Initialize(); |
|
||||||
} |
|
||||||
|
|
||||||
private void Initialize() |
|
||||||
{ |
|
||||||
var list = this._provider.GetServices<IJobHandler>(); |
|
||||||
if (list == null || !list.Any()) |
|
||||||
{ |
|
||||||
throw new TypeLoadException("IJobHandlers are not found in IServiceCollection"); |
|
||||||
} |
|
||||||
|
|
||||||
foreach (var handler in list) |
|
||||||
{ |
|
||||||
var jobHandlerAttr = handler.GetType().GetCustomAttribute<JobHandlerAttribute>(); |
|
||||||
var handlerName = jobHandlerAttr == null ? handler.GetType().Name : jobHandlerAttr.Name; |
|
||||||
if (handlersCache.ContainsKey(handlerName)) |
|
||||||
{ |
|
||||||
throw new Exception($"same IJobHandler' name: [{handlerName}]"); |
|
||||||
} |
|
||||||
handlersCache.Add(handlerName,handler); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
public IJobHandler GetJobHandler(string handlerName) |
|
||||||
{ |
|
||||||
if (handlersCache.ContainsKey(handlerName)) |
|
||||||
{ |
|
||||||
return handlersCache[handlerName]; |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,76 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Config; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using Microsoft.Extensions.Options; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// 执行器注册注册 |
|
||||||
/// </summary> |
|
||||||
public class ExecutorRegistry : IExecutorRegistry |
|
||||||
{ |
|
||||||
private readonly AdminClient _adminClient; |
|
||||||
private readonly XxlJobExecutorOptions _options; |
|
||||||
private readonly ILogger<ExecutorRegistry> _logger; |
|
||||||
|
|
||||||
public ExecutorRegistry(AdminClient adminClient, IOptions<XxlJobExecutorOptions> optionsAccessor, ILogger<ExecutorRegistry> logger) |
|
||||||
{ |
|
||||||
Preconditions.CheckNotNull(optionsAccessor, "XxlJobExecutorOptions"); |
|
||||||
Preconditions.CheckNotNull(optionsAccessor.Value, "XxlJobExecutorOptions"); |
|
||||||
_adminClient = adminClient; |
|
||||||
_options = optionsAccessor.Value; |
|
||||||
if (string.IsNullOrEmpty(_options.SpecialBindAddress)) |
|
||||||
{ |
|
||||||
_options.SpecialBindAddress = IPUtility.GetLocalIntranetIP().MapToIPv4().ToString(); |
|
||||||
} |
|
||||||
_logger = logger; |
|
||||||
} |
|
||||||
|
|
||||||
public async Task RegistryAsync(CancellationToken cancellationToken) |
|
||||||
{ |
|
||||||
var registryParam = new RegistryParam { |
|
||||||
RegistryGroup = "EXECUTOR", |
|
||||||
RegistryKey = _options.AppName, |
|
||||||
RegistryValue = string.IsNullOrEmpty(_options.SpecialBindUrl)? |
|
||||||
$"http://{_options.SpecialBindAddress}:{_options.Port}/" : _options.SpecialBindUrl |
|
||||||
}; |
|
||||||
|
|
||||||
_logger.LogInformation(">>>>>>>> start registry to admin <<<<<<<<"); |
|
||||||
|
|
||||||
var errorTimes = 0; |
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested) |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
var ret = await _adminClient.Registry(registryParam); |
|
||||||
_logger.LogDebug("registry last result:{0}", ret?.Code); |
|
||||||
errorTimes = 0; |
|
||||||
await Task.Delay(Constants.RegistryInterval, cancellationToken); |
|
||||||
} |
|
||||||
catch (TaskCanceledException) |
|
||||||
{ |
|
||||||
_logger.LogInformation(">>>>> Application Stopping....<<<<<"); |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
errorTimes++; |
|
||||||
await Task.Delay(Constants.RegistryInterval, cancellationToken); |
|
||||||
_logger.LogError(ex, "registry error:{0},{1} Times", ex.Message, errorTimes); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_logger.LogInformation(">>>>>>>> end registry to admin <<<<<<<<"); |
|
||||||
|
|
||||||
_logger.LogInformation(">>>>>>>> start remove registry to admin <<<<<<<<"); |
|
||||||
|
|
||||||
var removeRet = await this._adminClient.RegistryRemove(registryParam); |
|
||||||
_logger.LogInformation("remove registry last result:{0}", removeRet?.Code); |
|
||||||
_logger.LogInformation(">>>>>>>> end remove registry to admin <<<<<<<<"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,63 +0,0 @@ |
|||||||
using System; |
|
||||||
using DotXxlJob.Core.Config; |
|
||||||
using DotXxlJob.Core.DefaultHandlers; |
|
||||||
using DotXxlJob.Core.Queue; |
|
||||||
using Microsoft.Extensions.Configuration; |
|
||||||
using Microsoft.Extensions.DependencyInjection; |
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|
||||||
using Microsoft.Extensions.Hosting; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public static class ServiceCollectionExtensions |
|
||||||
{ |
|
||||||
public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services,IConfiguration configuration) |
|
||||||
{ |
|
||||||
services.AddLogging(); |
|
||||||
services.AddOptions(); |
|
||||||
services.Configure<XxlJobExecutorOptions>(configuration.GetSection("xxlJob")) |
|
||||||
.AddXxlJobExecutorServiceDependency(); |
|
||||||
|
|
||||||
return services; |
|
||||||
} |
|
||||||
public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services,Action<XxlJobExecutorOptions> configAction) |
|
||||||
{ |
|
||||||
services.AddLogging(); |
|
||||||
services.AddOptions(); |
|
||||||
services.Configure(configAction).AddXxlJobExecutorServiceDependency(); |
|
||||||
return services; |
|
||||||
} |
|
||||||
|
|
||||||
public static IServiceCollection AddDefaultXxlJobHandlers(this IServiceCollection services) |
|
||||||
{ |
|
||||||
services.AddSingleton<IJobHandler,SimpleHttpJobHandler>(); |
|
||||||
return services; |
|
||||||
} |
|
||||||
public static IServiceCollection AddAutoRegistry(this IServiceCollection services) |
|
||||||
{ |
|
||||||
services.AddSingleton<IExecutorRegistry,ExecutorRegistry>() |
|
||||||
.AddSingleton<IHostedService,JobsExecuteHostedService>(); |
|
||||||
return services; |
|
||||||
} |
|
||||||
|
|
||||||
private static IServiceCollection AddXxlJobExecutorServiceDependency(this IServiceCollection services) |
|
||||||
{ |
|
||||||
|
|
||||||
//可在外部提前注册对应实现,并替换默认实现 |
|
||||||
services.TryAddSingleton<IJobLogger, JobLogger>(); |
|
||||||
services.TryAddSingleton<IJobHandlerFactory,DefaultJobHandlerFactory >(); |
|
||||||
services.TryAddSingleton<IExecutorRegistry, ExecutorRegistry>(); |
|
||||||
|
|
||||||
services.AddHttpClient("DotXxlJobClient"); |
|
||||||
services.AddSingleton<JobDispatcher>(); |
|
||||||
services.AddSingleton<TaskExecutorFactory>(); |
|
||||||
services.AddSingleton<XxlRestfulServiceHandler>(); |
|
||||||
services.AddSingleton<CallbackTaskQueue>(); |
|
||||||
services.AddSingleton<AdminClient>(); |
|
||||||
services.AddSingleton<ITaskExecutor, TaskExecutors.BeanTaskExecutor>(); |
|
||||||
|
|
||||||
return services; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,25 +0,0 @@ |
|||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using Microsoft.Extensions.Hosting; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// NOTE: 负责启动Executor服务,和进行服务注册的宿主服务 |
|
||||||
/// </summary> |
|
||||||
public class JobsExecuteHostedService:BackgroundService |
|
||||||
{ |
|
||||||
private readonly IExecutorRegistry _registry; |
|
||||||
|
|
||||||
public JobsExecuteHostedService(IExecutorRegistry registry) |
|
||||||
{ |
|
||||||
this._registry = registry; |
|
||||||
} |
|
||||||
|
|
||||||
protected override Task ExecuteAsync(CancellationToken stoppingToken) |
|
||||||
{ |
|
||||||
return this._registry.RegistryAsync(stoppingToken); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public interface IExecutorRegistry |
|
||||||
{ |
|
||||||
|
|
||||||
Task RegistryAsync(CancellationToken cancellationToken); |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,26 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public abstract class AbstractJobHandler:IJobHandler |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// |
|
||||||
/// </summary> |
|
||||||
/// <param name="param"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public abstract Task<ReturnT> Execute(JobExecuteContext context); |
|
||||||
|
|
||||||
|
|
||||||
public virtual void Dispose() |
|
||||||
{ |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public interface IJobHandler:IDisposable |
|
||||||
{ |
|
||||||
Task<ReturnT> Execute(JobExecuteContext context); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public interface IJobHandlerFactory |
|
||||||
{ |
|
||||||
IJobHandler GetJobHandler(string handlerName); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,60 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
internal static class Constants |
|
||||||
{ |
|
||||||
public const string RpcRequestJavaFullName = "com.xxl.rpc.remoting.net.params.XxlRpcRequest"; |
|
||||||
public const string RpcResponseJavaFullName = "com.xxl.rpc.remoting.net.params.XxlRpcResponse"; |
|
||||||
|
|
||||||
public const string RegistryParamJavaFullName = "com.xxl.job.core.biz.model.RegistryParam"; |
|
||||||
public const string ReturnTJavaFullName = "com.xxl.job.core.biz.model.ReturnT"; |
|
||||||
|
|
||||||
public const string TriggerParamJavaFullName = "com.xxl.job.core.biz.model.TriggerParam"; |
|
||||||
public const string HandleCallbackParamJavaFullName = "com.xxl.job.core.biz.model.HandleCallbackParam"; |
|
||||||
public const string LogResultJavaFullName = "com.xxl.job.core.biz.model.LogResult"; |
|
||||||
|
|
||||||
|
|
||||||
public const string JavaClassFulName = "java.lang.Class"; |
|
||||||
public const string JavaListFulName = "java.util.List"; |
|
||||||
|
|
||||||
public const string XxlJobRetryLogsFile = "xxl-job-retry.log"; |
|
||||||
public const string HandleLogsDirectory = "HandlerLogs"; |
|
||||||
|
|
||||||
public const string DefaultHttpClientName = "DotXxlJobHttpClient"; |
|
||||||
|
|
||||||
public const int DefaultLogRetentionDays = 30; |
|
||||||
|
|
||||||
public static TimeSpan RpcRequestExpireTimeSpan = TimeSpan.FromMinutes(3); |
|
||||||
|
|
||||||
public static TimeSpan RegistryInterval = TimeSpan.FromSeconds(60); |
|
||||||
|
|
||||||
public const int MaxCallbackRetryTimes = 10; |
|
||||||
//每次回调最多发送几条记录 |
|
||||||
public const int MaxCallbackRecordsPerRequest =5; |
|
||||||
public static TimeSpan CallbackRetryInterval = TimeSpan.FromSeconds(600); |
|
||||||
|
|
||||||
//Admin集群机器请求默认超时时间 |
|
||||||
//public static TimeSpan AdminServerDefaultTimeout = TimeSpan.FromSeconds(15); |
|
||||||
//Admin集群中的某台机器熔断后间隔多长时间再重试 |
|
||||||
public static TimeSpan AdminServerReconnectInterval = TimeSpan.FromMinutes(3); |
|
||||||
//Admin集群中的某台机器请求失败多少次后熔断 |
|
||||||
public const int AdminServerCircuitFailedTimes = 3; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class GlueType |
|
||||||
{ |
|
||||||
public const string BEAN = "BEAN"; |
|
||||||
} |
|
||||||
|
|
||||||
public static class ExecutorBlockStrategy |
|
||||||
{ |
|
||||||
public const string SERIAL_EXECUTION = "SERIAL_EXECUTION"; |
|
||||||
|
|
||||||
public const string DISCARD_LATER = "DISCARD_LATER"; |
|
||||||
|
|
||||||
public const string COVER_EARLY = "COVER_EARLY"; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,135 +0,0 @@ |
|||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Net; |
|
||||||
using System.Net.NetworkInformation; |
|
||||||
using System.Net.Sockets; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// ip utility |
|
||||||
/// </summary> |
|
||||||
internal static class IPUtility |
|
||||||
{ |
|
||||||
#region Private Members |
|
||||||
/// <summary> |
|
||||||
/// A类: 10.0.0.0-10.255.255.255 |
|
||||||
/// </summary> |
|
||||||
private static long ipABegin, ipAEnd; |
|
||||||
/// <summary> |
|
||||||
/// B类: 172.16.0.0-172.31.255.255 |
|
||||||
/// </summary> |
|
||||||
private static long ipBBegin, ipBEnd; |
|
||||||
/// <summary> |
|
||||||
/// C类: 192.168.0.0-192.168.255.255 |
|
||||||
/// </summary> |
|
||||||
private static long ipCBegin, ipCEnd; |
|
||||||
#endregion |
|
||||||
|
|
||||||
#region Constructors |
|
||||||
/// <summary> |
|
||||||
/// static new |
|
||||||
/// </summary> |
|
||||||
static IPUtility() |
|
||||||
{ |
|
||||||
ipABegin = ConvertToNumber("10.0.0.0"); |
|
||||||
ipAEnd = ConvertToNumber("10.255.255.255"); |
|
||||||
|
|
||||||
ipBBegin = ConvertToNumber("172.16.0.0"); |
|
||||||
ipBEnd = ConvertToNumber("172.31.255.255"); |
|
||||||
|
|
||||||
ipCBegin = ConvertToNumber("192.168.0.0"); |
|
||||||
ipCEnd = ConvertToNumber("192.168.255.255"); |
|
||||||
} |
|
||||||
#endregion |
|
||||||
|
|
||||||
#region Public Methods |
|
||||||
/// <summary> |
|
||||||
/// ip address convert to long |
|
||||||
/// </summary> |
|
||||||
/// <param name="ipAddress"></param> |
|
||||||
/// <returns></returns> |
|
||||||
private static long ConvertToNumber(string ipAddress) |
|
||||||
{ |
|
||||||
return ConvertToNumber(IPAddress.Parse(ipAddress)); |
|
||||||
} |
|
||||||
/// <summary> |
|
||||||
/// ip address convert to long |
|
||||||
/// </summary> |
|
||||||
/// <param name="ipAddress"></param> |
|
||||||
/// <returns></returns> |
|
||||||
private static long ConvertToNumber(IPAddress ipAddress) |
|
||||||
{ |
|
||||||
var bytes = ipAddress.GetAddressBytes(); |
|
||||||
return bytes[0] * 256 * 256 * 256 + bytes[1] * 256 * 256 + bytes[2] * 256 + bytes[3]; |
|
||||||
} |
|
||||||
/// <summary> |
|
||||||
/// true表示为内网IP |
|
||||||
/// </summary> |
|
||||||
/// <param name="ipAddress"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public static bool IsIntranet(string ipAddress) |
|
||||||
{ |
|
||||||
return IsIntranet(ConvertToNumber(ipAddress)); |
|
||||||
} |
|
||||||
/// <summary> |
|
||||||
/// true表示为内网IP |
|
||||||
/// </summary> |
|
||||||
/// <param name="ipAddress"></param> |
|
||||||
/// <returns></returns> |
|
||||||
private static bool IsIntranet(IPAddress ipAddress) |
|
||||||
{ |
|
||||||
return IsIntranet(ConvertToNumber(ipAddress)); |
|
||||||
} |
|
||||||
/// <summary> |
|
||||||
/// true表示为内网IP |
|
||||||
/// </summary> |
|
||||||
/// <param name="longIP"></param> |
|
||||||
/// <returns></returns> |
|
||||||
private static bool IsIntranet(long longIP) |
|
||||||
{ |
|
||||||
return ((longIP >= ipABegin) && (longIP <= ipAEnd) || |
|
||||||
(longIP >= ipBBegin) && (longIP <= ipBEnd) || |
|
||||||
(longIP >= ipCBegin) && (longIP <= ipCEnd)); |
|
||||||
} |
|
||||||
/// <summary> |
|
||||||
/// 获取本机内网IP |
|
||||||
/// </summary> |
|
||||||
/// <returns></returns> |
|
||||||
public static IPAddress GetLocalIntranetIP() |
|
||||||
{ |
|
||||||
return NetworkInterface |
|
||||||
.GetAllNetworkInterfaces() |
|
||||||
.Select(p => p.GetIPProperties()) |
|
||||||
.SelectMany(p => |
|
||||||
p.UnicastAddresses |
|
||||||
).FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork |
|
||||||
&& !IPAddress.IsLoopback(p.Address) |
|
||||||
&& IsIntranet(p.Address))?.Address; |
|
||||||
} |
|
||||||
/// <summary> |
|
||||||
/// 获取本机内网IP列表 |
|
||||||
/// </summary> |
|
||||||
/// <returns></returns> |
|
||||||
public static List<IPAddress> GetLocalIntranetIPList() |
|
||||||
{ |
|
||||||
var infList =NetworkInterface.GetAllNetworkInterfaces() |
|
||||||
.Select(p => p.GetIPProperties()) |
|
||||||
.SelectMany(p => p.UnicastAddresses) |
|
||||||
.Where(p => |
|
||||||
p.Address.AddressFamily == AddressFamily.InterNetwork |
|
||||||
&& !IPAddress.IsLoopback(p.Address) |
|
||||||
&& IsIntranet(p.Address) |
|
||||||
); |
|
||||||
|
|
||||||
var result = new List<IPAddress>(); |
|
||||||
foreach (var child in infList) |
|
||||||
{ |
|
||||||
result.Add(child.Address); |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
|
||||||
} |
|
||||||
#endregion |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,87 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// Utility methods to simplify checking preconditions in the code. |
|
||||||
/// </summary> |
|
||||||
internal static class Preconditions |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// Throws <see cref="ArgumentException"/> if condition is false. |
|
||||||
/// </summary> |
|
||||||
/// <param name="condition">The condition.</param> |
|
||||||
public static void CheckArgument(bool condition) |
|
||||||
{ |
|
||||||
if (!condition) |
|
||||||
{ |
|
||||||
throw new ArgumentException(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Throws <see cref="ArgumentException"/> with given message if condition is false. |
|
||||||
/// </summary> |
|
||||||
/// <param name="condition">The condition.</param> |
|
||||||
/// <param name="errorMessage">The error message.</param> |
|
||||||
public static void CheckArgument(bool condition, string errorMessage) |
|
||||||
{ |
|
||||||
if (!condition) |
|
||||||
{ |
|
||||||
throw new ArgumentException(errorMessage); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Throws <see cref="ArgumentNullException"/> if reference is null. |
|
||||||
/// </summary> |
|
||||||
/// <param name="reference">The reference.</param> |
|
||||||
public static T CheckNotNull<T>(T reference) |
|
||||||
{ |
|
||||||
if (reference == null) |
|
||||||
{ |
|
||||||
throw new ArgumentNullException(); |
|
||||||
} |
|
||||||
return reference; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Throws <see cref="ArgumentNullException"/> if reference is null. |
|
||||||
/// </summary> |
|
||||||
/// <param name="reference">The reference.</param> |
|
||||||
/// <param name="paramName">The parameter name.</param> |
|
||||||
public static T CheckNotNull<T>(T reference, string paramName) |
|
||||||
{ |
|
||||||
if (reference == null) |
|
||||||
{ |
|
||||||
throw new ArgumentNullException(paramName); |
|
||||||
} |
|
||||||
return reference; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Throws <see cref="InvalidOperationException"/> if condition is false. |
|
||||||
/// </summary> |
|
||||||
/// <param name="condition">The condition.</param> |
|
||||||
public static void CheckState(bool condition) |
|
||||||
{ |
|
||||||
if (!condition) |
|
||||||
{ |
|
||||||
throw new InvalidOperationException(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Throws <see cref="InvalidOperationException"/> with given message if condition is false. |
|
||||||
/// </summary> |
|
||||||
/// <param name="condition">The condition.</param> |
|
||||||
/// <param name="errorMessage">The error message.</param> |
|
||||||
public static void CheckState(bool condition, string errorMessage) |
|
||||||
{ |
|
||||||
if (!condition) |
|
||||||
{ |
|
||||||
throw new InvalidOperationException(errorMessage); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,158 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Concurrent; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using DotXxlJob.Core.Queue; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// 负责实际的JOB轮询 |
|
||||||
/// </summary> |
|
||||||
public class JobDispatcher |
|
||||||
{ |
|
||||||
private readonly TaskExecutorFactory _executorFactory; |
|
||||||
private readonly CallbackTaskQueue _callbackTaskQueue; |
|
||||||
private readonly IJobLogger _jobLogger; |
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<int,JobTaskQueue> RUNNING_QUEUE = new ConcurrentDictionary<int, JobTaskQueue>(); |
|
||||||
|
|
||||||
|
|
||||||
private readonly ILogger<JobTaskQueue> _jobQueueLogger; |
|
||||||
private readonly ILogger<JobDispatcher> _logger; |
|
||||||
public JobDispatcher( |
|
||||||
TaskExecutorFactory executorFactory, |
|
||||||
CallbackTaskQueue callbackTaskQueue, |
|
||||||
IJobLogger jobLogger, |
|
||||||
ILoggerFactory loggerFactory |
|
||||||
) |
|
||||||
{ |
|
||||||
this. _executorFactory = executorFactory; |
|
||||||
this. _callbackTaskQueue = callbackTaskQueue; |
|
||||||
this._jobLogger = jobLogger; |
|
||||||
|
|
||||||
|
|
||||||
this._jobQueueLogger = loggerFactory.CreateLogger<JobTaskQueue>(); |
|
||||||
this._logger = loggerFactory.CreateLogger<JobDispatcher>(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 尝试移除JobTask |
|
||||||
/// </summary> |
|
||||||
/// <param name="jobId"></param> |
|
||||||
/// <param name="reason"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public bool TryRemoveJobTask(int jobId) |
|
||||||
{ |
|
||||||
if (RUNNING_QUEUE.TryGetValue(jobId, out var jobQueue)) |
|
||||||
{ |
|
||||||
jobQueue.Stop(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 执行队列,并快速返回结果 |
|
||||||
/// </summary> |
|
||||||
/// <param name="triggerParam"></param> |
|
||||||
/// <returns></returns> |
|
||||||
/// <exception cref="NotImplementedException"></exception> |
|
||||||
public ReturnT Execute(TriggerParam triggerParam) |
|
||||||
{ |
|
||||||
|
|
||||||
var executor = this._executorFactory.GetTaskExecutor(triggerParam.GlueType); |
|
||||||
if (executor == null) |
|
||||||
{ |
|
||||||
return ReturnT.Failed($"glueType[{triggerParam.GlueType}] is not supported "); |
|
||||||
} |
|
||||||
|
|
||||||
// 1. 根据JobId 获取 TaskQueue; 用于判断是否有正在执行的任务 |
|
||||||
if (RUNNING_QUEUE.TryGetValue(triggerParam.JobId, out var taskQueue)) |
|
||||||
{ |
|
||||||
if (taskQueue.Executor != executor) //任务执行器变更 |
|
||||||
{ |
|
||||||
return ChangeJobQueue(triggerParam, executor); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (taskQueue != null) //旧任务还在执行,判断执行策略 |
|
||||||
{ |
|
||||||
//丢弃后续的 |
|
||||||
if (Constants.ExecutorBlockStrategy.DISCARD_LATER == triggerParam.ExecutorBlockStrategy) |
|
||||||
{ |
|
||||||
//存在还没执行完成的任务 |
|
||||||
if (taskQueue.IsRunning()) |
|
||||||
{ |
|
||||||
return ReturnT.Failed($"block strategy effect:{triggerParam.ExecutorBlockStrategy}"); |
|
||||||
} |
|
||||||
//否则还是继续做 |
|
||||||
} |
|
||||||
//覆盖较早的 |
|
||||||
if (Constants.ExecutorBlockStrategy.COVER_EARLY == triggerParam.ExecutorBlockStrategy) |
|
||||||
{ |
|
||||||
return taskQueue.Replace(triggerParam); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return PushJobQueue(triggerParam, executor); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// IdleBeat |
|
||||||
/// </summary> |
|
||||||
/// <param name="jobId"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public ReturnT IdleBeat(int jobId) |
|
||||||
{ |
|
||||||
return RUNNING_QUEUE.ContainsKey(jobId) ? |
|
||||||
new ReturnT(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.") |
|
||||||
: ReturnT.SUCCESS; |
|
||||||
} |
|
||||||
|
|
||||||
private void TriggerCallback(object sender, HandleCallbackParam callbackParam) |
|
||||||
{ |
|
||||||
this._callbackTaskQueue.Push(callbackParam); |
|
||||||
} |
|
||||||
|
|
||||||
private ReturnT PushJobQueue(TriggerParam triggerParam, ITaskExecutor executor) |
|
||||||
{ |
|
||||||
|
|
||||||
if (RUNNING_QUEUE.TryGetValue(triggerParam.JobId,out var jobQueue)) |
|
||||||
{ |
|
||||||
return jobQueue.Push(triggerParam); |
|
||||||
} |
|
||||||
|
|
||||||
//NewJobId |
|
||||||
jobQueue = new JobTaskQueue ( executor,this._jobLogger, this._jobQueueLogger); |
|
||||||
jobQueue.CallBack += TriggerCallback; |
|
||||||
if (RUNNING_QUEUE.TryAdd(triggerParam.JobId, jobQueue)) |
|
||||||
{ |
|
||||||
return jobQueue.Push(triggerParam); |
|
||||||
} |
|
||||||
return ReturnT.Failed("add running queue executor error"); |
|
||||||
} |
|
||||||
|
|
||||||
private ReturnT ChangeJobQueue(TriggerParam triggerParam, ITaskExecutor executor) |
|
||||||
{ |
|
||||||
|
|
||||||
if (RUNNING_QUEUE.TryRemove(triggerParam.JobId, out var oldJobTask)) |
|
||||||
{ |
|
||||||
oldJobTask.CallBack -= TriggerCallback; |
|
||||||
oldJobTask.Dispose(); //释放原来的资源 |
|
||||||
} |
|
||||||
|
|
||||||
JobTaskQueue jobQueue = new JobTaskQueue ( executor,this._jobLogger, this._jobQueueLogger); |
|
||||||
jobQueue.CallBack += TriggerCallback; |
|
||||||
if (RUNNING_QUEUE.TryAdd(triggerParam.JobId, jobQueue)) |
|
||||||
{ |
|
||||||
return jobQueue.Push(triggerParam); |
|
||||||
} |
|
||||||
return ReturnT.Failed(" replace running queue executor error"); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
using System.Reflection; |
|
||||||
using Utf8Json; |
|
||||||
using Utf8Json.Formatters; |
|
||||||
using Utf8Json.Resolvers; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Json |
|
||||||
{ |
|
||||||
public class ProjectDefaultResolver : IJsonFormatterResolver |
|
||||||
{ |
|
||||||
public static IJsonFormatterResolver Instance = new ProjectDefaultResolver(); |
|
||||||
|
|
||||||
// configure your resolver and formatters. |
|
||||||
static readonly IJsonFormatter[] formatters = { |
|
||||||
new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"), |
|
||||||
new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss") |
|
||||||
}; |
|
||||||
|
|
||||||
static readonly IJsonFormatterResolver[] resolvers = new[] |
|
||||||
{ |
|
||||||
EnumResolver.UnderlyingValue, |
|
||||||
StandardResolver.AllowPrivateExcludeNullSnakeCase |
|
||||||
}; |
|
||||||
|
|
||||||
ProjectDefaultResolver() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
public IJsonFormatter<T> GetFormatter<T>() |
|
||||||
{ |
|
||||||
return FormatterCache<T>.formatter; |
|
||||||
} |
|
||||||
|
|
||||||
static class FormatterCache<T> |
|
||||||
{ |
|
||||||
public static readonly IJsonFormatter<T> formatter; |
|
||||||
|
|
||||||
static FormatterCache() |
|
||||||
{ |
|
||||||
foreach (var item in formatters) |
|
||||||
{ |
|
||||||
foreach (var implInterface in item.GetType().GetTypeInfo().ImplementedInterfaces) |
|
||||||
{ |
|
||||||
var ti = implInterface.GetTypeInfo(); |
|
||||||
if (ti.IsGenericType && ti.GenericTypeArguments[0] == typeof(T)) |
|
||||||
{ |
|
||||||
formatter = (IJsonFormatter<T>)item; |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
foreach (var item in resolvers) |
|
||||||
{ |
|
||||||
var f = item.GetFormatter<T>(); |
|
||||||
if (f != null) |
|
||||||
{ |
|
||||||
formatter = f; |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,23 +0,0 @@ |
|||||||
using System; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public interface IJobLogger |
|
||||||
{ |
|
||||||
|
|
||||||
void SetLogFile(long logTime, long logId); |
|
||||||
|
|
||||||
void Log(string pattern, params object[] format); |
|
||||||
|
|
||||||
|
|
||||||
void LogError(Exception ex); |
|
||||||
|
|
||||||
|
|
||||||
LogResult ReadLog(long logTime, long logId, int fromLine); |
|
||||||
|
|
||||||
|
|
||||||
void LogSpecialFile(long logTime, long logId, string pattern, params object[] format); |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,194 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.IO; |
|
||||||
using System.Text; |
|
||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Config; |
|
||||||
using DotXxlJob.Core.Extensions; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using Microsoft.Extensions.Options; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public class JobLogger:IJobLogger |
|
||||||
{ |
|
||||||
private readonly ILogger<JobLogger> _logger; |
|
||||||
|
|
||||||
private readonly AsyncLocal<string> LogFileName = new AsyncLocal<string>(); |
|
||||||
|
|
||||||
private readonly XxlJobExecutorOptions _options; |
|
||||||
public JobLogger(IOptions<XxlJobExecutorOptions> optionsAccessor,ILogger<JobLogger> logger) |
|
||||||
{ |
|
||||||
this._logger = logger; |
|
||||||
this._options = optionsAccessor.Value; |
|
||||||
} |
|
||||||
|
|
||||||
public void SetLogFile(long logTime, long logId) |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
var filePath = MakeLogFileName(logTime, logId); |
|
||||||
var dir = Path.GetDirectoryName(filePath); |
|
||||||
if (!Directory.Exists(dir)) |
|
||||||
{ |
|
||||||
Directory.CreateDirectory(dir); |
|
||||||
CleanOldLogs(); |
|
||||||
} |
|
||||||
LogFileName.Value = filePath; |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
_logger.LogError(ex, "SetLogFileName error."); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public void Log(string pattern, params object[] format) |
|
||||||
{ |
|
||||||
string appendLog; |
|
||||||
if (format == null || format.Length == 0) |
|
||||||
{ |
|
||||||
appendLog = pattern; |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
appendLog = string.Format(pattern, format); |
|
||||||
} |
|
||||||
|
|
||||||
var callInfo = new StackTrace(true).GetFrame(1); |
|
||||||
LogDetail(GetLogFileName(), callInfo, appendLog); |
|
||||||
} |
|
||||||
|
|
||||||
public void LogError(Exception ex) |
|
||||||
{ |
|
||||||
var callInfo = new StackTrace(true).GetFrame(1); |
|
||||||
LogDetail(GetLogFileName(), callInfo, ex.Message + ex.StackTrace); |
|
||||||
} |
|
||||||
|
|
||||||
public LogResult ReadLog(long logTime, long logId, int fromLine) |
|
||||||
{ |
|
||||||
var filePath = MakeLogFileName(logTime, logId); |
|
||||||
if (string.IsNullOrEmpty(filePath)) |
|
||||||
{ |
|
||||||
return new LogResult(fromLine, 0, "readLog fail, logFile not found", true); |
|
||||||
} |
|
||||||
if (!File.Exists(filePath)) |
|
||||||
{ |
|
||||||
return new LogResult(fromLine, 0, "readLog fail, logFile not exists", true); |
|
||||||
} |
|
||||||
|
|
||||||
// read file |
|
||||||
var logContentBuffer = new StringBuilder(); |
|
||||||
int toLineNum = 0; |
|
||||||
try |
|
||||||
{ |
|
||||||
using (var reader = new StreamReader(filePath, Encoding.UTF8)) |
|
||||||
{ |
|
||||||
string line; |
|
||||||
while ((line = reader.ReadLine()) != null) |
|
||||||
{ |
|
||||||
toLineNum++; |
|
||||||
if (toLineNum >= fromLine) |
|
||||||
{ |
|
||||||
logContentBuffer.AppendLine(line); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
this._logger.LogError(ex, "ReadLog error."); |
|
||||||
} |
|
||||||
|
|
||||||
// result |
|
||||||
var logResult = new LogResult(fromLine, toLineNum, logContentBuffer.ToString(), false); |
|
||||||
return logResult; |
|
||||||
} |
|
||||||
|
|
||||||
public void LogSpecialFile(long logTime, long logId, string pattern, params object[] format) |
|
||||||
{ |
|
||||||
var filePath = MakeLogFileName(logTime, logId); |
|
||||||
var callInfo = new StackTrace(true).GetFrame(1); |
|
||||||
var content = string.Format(pattern, format); |
|
||||||
LogDetail(filePath, callInfo, content); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private string GetLogFileName() |
|
||||||
{ |
|
||||||
return LogFileName.Value; |
|
||||||
} |
|
||||||
private string MakeLogFileName(long logDateTime, long logId) |
|
||||||
{ |
|
||||||
//log fileName like: logPath/HandlerLogs/yyyy-MM-dd/9999.log |
|
||||||
return Path.Combine(this._options.LogPath, Constants.HandleLogsDirectory, |
|
||||||
logDateTime.FromMilliseconds().ToString("yyyy-MM-dd"), $"{logId}.log"); |
|
||||||
} |
|
||||||
private void LogDetail(string logFileName, StackFrame callInfo, string appendLog) |
|
||||||
{ |
|
||||||
if (string.IsNullOrEmpty(logFileName)) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
var stringBuffer = new StringBuilder(); |
|
||||||
stringBuffer |
|
||||||
.Append(DateTime.Now.ToString("s")).Append(" ") |
|
||||||
.Append("[" + callInfo.GetMethod().DeclaringType.FullName + "#" + callInfo.GetMethod().Name + "]").Append("-") |
|
||||||
.Append("[line " + callInfo.GetFileLineNumber() + "]").Append("-") |
|
||||||
.Append("[thread " + Thread.CurrentThread.ManagedThreadId + "]").Append(" ") |
|
||||||
.Append(appendLog ?? string.Empty) |
|
||||||
.AppendLine(); |
|
||||||
|
|
||||||
var formatAppendLog = stringBuffer.ToString(); |
|
||||||
|
|
||||||
try |
|
||||||
{ |
|
||||||
File.AppendAllText(logFileName, formatAppendLog, Encoding.UTF8); |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
this._logger.LogError(ex, "LogDetail error"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void CleanOldLogs() |
|
||||||
{ |
|
||||||
if (this._options.LogRetentionDays <= 0) |
|
||||||
{ |
|
||||||
this._options.LogRetentionDays = Constants.DefaultLogRetentionDays; |
|
||||||
} |
|
||||||
|
|
||||||
Task.Run(() => |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
var handlerLogsDir = new DirectoryInfo(Path.Combine(this._options.LogPath, Constants.HandleLogsDirectory)); |
|
||||||
if (!handlerLogsDir.Exists) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
var today = DateTime.UtcNow.Date; |
|
||||||
foreach (var dir in handlerLogsDir.GetDirectories()) |
|
||||||
{ |
|
||||||
if (DateTime.TryParse(dir.Name, out var dirDate)) |
|
||||||
{ |
|
||||||
if (today.Subtract(dirDate.Date).Days > this._options.LogRetentionDays) |
|
||||||
{ |
|
||||||
dir.Delete(true); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
this._logger.LogError(ex, "CleanOldLogs error."); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,39 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
public class AddressEntry |
|
||||||
{ |
|
||||||
public string RequestUri { get; set; } |
|
||||||
|
|
||||||
private DateTime? LastFailedTime { get; set; } |
|
||||||
|
|
||||||
private int FailedTimes { get; set; } |
|
||||||
|
|
||||||
public bool CheckAccessible() |
|
||||||
{ |
|
||||||
if (LastFailedTime == null) |
|
||||||
return true; |
|
||||||
|
|
||||||
if (DateTime.UtcNow.Subtract(LastFailedTime.Value) > Constants.AdminServerReconnectInterval) |
|
||||||
return true; |
|
||||||
|
|
||||||
if (FailedTimes < Constants.AdminServerCircuitFailedTimes) |
|
||||||
return true; |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
public void Reset() |
|
||||||
{ |
|
||||||
LastFailedTime = null; |
|
||||||
FailedTimes = 0; |
|
||||||
} |
|
||||||
|
|
||||||
public void SetFail() |
|
||||||
{ |
|
||||||
LastFailedTime = DateTime.UtcNow; |
|
||||||
FailedTimes++; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,58 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.HandleCallbackParamJavaFullName)] |
|
||||||
public class HandleCallbackParam |
|
||||||
{ |
|
||||||
public HandleCallbackParam() |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
||||||
public HandleCallbackParam(TriggerParam triggerParam, ReturnT result) |
|
||||||
{ |
|
||||||
this.LogId = triggerParam.LogId; |
|
||||||
this.LogDateTime = triggerParam.LogDateTime; |
|
||||||
this.ExecuteResult = result; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public int CallbackRetryTimes { get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "logId",Order = 1)] |
|
||||||
public long LogId { get; set; } |
|
||||||
[DataMember(Name = "logDateTim",Order = 2)] |
|
||||||
public long LogDateTime { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 2.3.0以前版本 |
|
||||||
/// </summary> |
|
||||||
[DataMember(Name = "executeResult",Order = 3)] |
|
||||||
public ReturnT ExecuteResult { get; set; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 2.3.0版本使用的参数 |
|
||||||
/// </summary> |
|
||||||
[DataMember(Name = "handleCode", Order = 4)] |
|
||||||
public int HandleCode { |
|
||||||
get { |
|
||||||
if(this.ExecuteResult != null) |
|
||||||
{ |
|
||||||
return this.ExecuteResult.Code; |
|
||||||
} |
|
||||||
return 500; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 2.3.0版本使用的参数 |
|
||||||
/// </summary> |
|
||||||
[DataMember(Name = "handleMsg", Order = 5)] |
|
||||||
public string HandleMsg { |
|
||||||
get { |
|
||||||
return this.ExecuteResult?.Msg; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.JavaClassFulName)] |
|
||||||
public class JavaClass |
|
||||||
{ |
|
||||||
[DataMember(Name = "name",Order = 1)] |
|
||||||
public string Name { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,17 +0,0 @@ |
|||||||
using System.Threading; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
public class JobExecuteContext |
|
||||||
{ |
|
||||||
public JobExecuteContext(IJobLogger jobLogger, string jobParameter, CancellationToken cancellationToken) |
|
||||||
{ |
|
||||||
this.JobLogger = jobLogger; |
|
||||||
this.JobParameter = jobParameter; |
|
||||||
this.cancellationToken = cancellationToken; |
|
||||||
} |
|
||||||
public string JobParameter { get; } |
|
||||||
public IJobLogger JobLogger { get; } |
|
||||||
public CancellationToken cancellationToken { get; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,26 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.LogResultJavaFullName)] |
|
||||||
public class LogResult |
|
||||||
{ |
|
||||||
|
|
||||||
public LogResult(int fromLine ,int toLine,string content,bool isEnd) |
|
||||||
{ |
|
||||||
this.FromLineNum = fromLine; |
|
||||||
this.ToLineNum = toLine; |
|
||||||
this.LogContent = content; |
|
||||||
this.IsEnd = isEnd; |
|
||||||
} |
|
||||||
|
|
||||||
[DataMember(Name = "fromLineNum",Order = 1)] |
|
||||||
public int FromLineNum { get; set; } |
|
||||||
[DataMember(Name = "toLineNum",Order = 2)] |
|
||||||
public int ToLineNum { get; set; } |
|
||||||
[DataMember(Name = "logContent",Order = 3)] |
|
||||||
public string LogContent { get; set; } |
|
||||||
[DataMember(Name = "isEnd",Order = 4)] |
|
||||||
public bool IsEnd { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,19 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.RegistryParamJavaFullName)] |
|
||||||
public class RegistryParam |
|
||||||
{ |
|
||||||
[DataMember(Name = "registryGroup", Order = 1)] |
|
||||||
public string RegistryGroup { get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "registryKey", Order = 2)] |
|
||||||
public string RegistryKey { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
[DataMember(Name = "registryValue", Order = 3)] |
|
||||||
public string RegistryValue { get; set; } |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,47 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.ReturnTJavaFullName)] |
|
||||||
public class ReturnT |
|
||||||
{ |
|
||||||
public const int SUCCESS_CODE = 200; |
|
||||||
public const int FAIL_CODE = 500; |
|
||||||
|
|
||||||
public static readonly ReturnT SUCCESS = new ReturnT(SUCCESS_CODE, null); |
|
||||||
public static readonly ReturnT FAIL = new ReturnT(FAIL_CODE, null); |
|
||||||
public static readonly ReturnT FAIL_TIMEOUT = new ReturnT(502, null); |
|
||||||
|
|
||||||
public ReturnT() { } |
|
||||||
|
|
||||||
public ReturnT(int code, string msg) |
|
||||||
{ |
|
||||||
Code = code; |
|
||||||
Msg = msg; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
[DataMember(Name = "code",Order = 1)] |
|
||||||
public int Code { get; set; } |
|
||||||
[DataMember(Name = "msg",Order = 2)] |
|
||||||
public string Msg { get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "content",Order = 3)] |
|
||||||
public object Content { get; set; } |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static ReturnT Failed(string msg) |
|
||||||
{ |
|
||||||
return new ReturnT(FAIL_CODE, msg); |
|
||||||
} |
|
||||||
public static ReturnT Success(string msg) |
|
||||||
{ |
|
||||||
return new ReturnT(SUCCESS_CODE, msg); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
@ -1,50 +0,0 @@ |
|||||||
using System.Collections.Generic; |
|
||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.RpcRequestJavaFullName)] |
|
||||||
public class RpcRequest |
|
||||||
{ |
|
||||||
/* |
|
||||||
requestId |
|
||||||
createMillisTime |
|
||||||
accessToken |
|
||||||
className |
|
||||||
methodName |
|
||||||
version |
|
||||||
parameterTypes |
|
||||||
parameters |
|
||||||
*/ |
|
||||||
[DataMember(Name = "requestId",Order = 1)] |
|
||||||
public string RequestId { get; set; } |
|
||||||
|
|
||||||
//[DataMember(Name = "serverAddress")] |
|
||||||
//public string ServerAddress{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "createMillisTime" ,Order = 2)] |
|
||||||
public long CreateMillisTime{ get; set; } |
|
||||||
|
|
||||||
|
|
||||||
[DataMember(Name = "accessToken" ,Order = 3)] |
|
||||||
public string AccessToken{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "className" ,Order = 4)] |
|
||||||
public string ClassName{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "methodName" ,Order = 5)] |
|
||||||
public string MethodName{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "version" ,Order = 6)] |
|
||||||
public string Version{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "parameterTypes",Order = 7)] |
|
||||||
public IList<object> ParameterTypes{ get; set; } |
|
||||||
|
|
||||||
|
|
||||||
[DataMember(Name = "parameters",Order = 8)] |
|
||||||
public IList<object> Parameters{ get; set; } |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,18 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
[DataContract(Name = Constants.RpcResponseJavaFullName)] |
|
||||||
public class RpcResponse |
|
||||||
{ |
|
||||||
[DataMember(Name = "requestId",Order = 1)] |
|
||||||
public string RequestId{ get; set; } |
|
||||||
[DataMember(Name = "errorMsg",Order = 2)] |
|
||||||
public string ErrorMsg { get; set; } |
|
||||||
[DataMember(Name = "result",Order = 3)] |
|
||||||
public object Result{ get; set; } |
|
||||||
|
|
||||||
|
|
||||||
public bool IsError => this.ErrorMsg != null; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Model |
|
||||||
{ |
|
||||||
|
|
||||||
[DataContract] |
|
||||||
public class TriggerParam |
|
||||||
{ |
|
||||||
//static readonly long SerialVersionUID = 42L; |
|
||||||
|
|
||||||
[DataMember(Name = "jobId", Order = 1)] |
|
||||||
public int JobId { get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "executorHandler", Order = 2)] |
|
||||||
public string ExecutorHandler { get; set; } |
|
||||||
[DataMember(Name = "executorParams", Order = 3)] |
|
||||||
public string ExecutorParams{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "executorBlockStrategy", Order = 4)] |
|
||||||
public string ExecutorBlockStrategy{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "executorTimeout", Order = 5)] |
|
||||||
public int ExecutorTimeout{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "logId",Order = 5)] |
|
||||||
public long LogId { get; set; } |
|
||||||
[DataMember(Name = "logDateTime", Order = 6)] |
|
||||||
public long LogDateTime{ get; set; } |
|
||||||
|
|
||||||
|
|
||||||
[DataMember(Name = "glueType",Order = 7)] |
|
||||||
public string GlueType{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "glueSource",Order = 8)] |
|
||||||
public string GlueSource{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "glueUpdatetime", Order = 9)] |
|
||||||
public long GlueUpdateTime{ get; set; } |
|
||||||
|
|
||||||
[DataMember(Name = "broadcastIndex",Order = 10)] |
|
||||||
public int BroadcastIndex{ get; set; } |
|
||||||
[DataMember(Name = "broadcastTotal",Order = 11)] |
|
||||||
public int BroadcastTotal{ get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,118 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Concurrent; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Config; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using Microsoft.Extensions.Options; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Queue |
|
||||||
{ |
|
||||||
public class CallbackTaskQueue:IDisposable |
|
||||||
{ |
|
||||||
private readonly AdminClient _adminClient; |
|
||||||
private readonly IJobLogger _jobLogger; |
|
||||||
private readonly RetryCallbackTaskQueue _retryQueue; |
|
||||||
private readonly ILogger<CallbackTaskQueue> _logger; |
|
||||||
private readonly ConcurrentQueue<HandleCallbackParam> taskQueue = new ConcurrentQueue<HandleCallbackParam>(); |
|
||||||
|
|
||||||
private bool _stop; |
|
||||||
|
|
||||||
private bool _isRunning; |
|
||||||
|
|
||||||
private int _callbackInterval; |
|
||||||
|
|
||||||
private Task _runTask; |
|
||||||
public CallbackTaskQueue(AdminClient adminClient,IJobLogger jobLogger,IOptions<XxlJobExecutorOptions> optionsAccessor |
|
||||||
, ILoggerFactory loggerFactory) |
|
||||||
{ |
|
||||||
_adminClient = adminClient; |
|
||||||
_jobLogger = jobLogger; |
|
||||||
|
|
||||||
_callbackInterval = optionsAccessor.Value.CallBackInterval; |
|
||||||
|
|
||||||
_retryQueue = new RetryCallbackTaskQueue(optionsAccessor.Value.LogPath, |
|
||||||
Push, |
|
||||||
loggerFactory.CreateLogger<RetryCallbackTaskQueue>()); |
|
||||||
|
|
||||||
_logger = loggerFactory.CreateLogger<CallbackTaskQueue>(); |
|
||||||
} |
|
||||||
|
|
||||||
public void Push(HandleCallbackParam callbackParam) |
|
||||||
{ |
|
||||||
taskQueue.Enqueue(callbackParam); |
|
||||||
StartCallBack(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ |
|
||||||
_stop = true; |
|
||||||
_retryQueue.Dispose(); |
|
||||||
_runTask?.GetAwaiter().GetResult(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private void StartCallBack() |
|
||||||
{ |
|
||||||
if ( _isRunning) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
_runTask = Task.Run(async () => |
|
||||||
{ |
|
||||||
_logger.LogDebug("start to callback"); |
|
||||||
_isRunning = true; |
|
||||||
while (!_stop) |
|
||||||
{ |
|
||||||
await DoCallBack(); |
|
||||||
if (taskQueue.IsEmpty) |
|
||||||
{ |
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(_callbackInterval)); |
|
||||||
} |
|
||||||
} |
|
||||||
_logger.LogDebug("end to callback"); |
|
||||||
_isRunning = false; |
|
||||||
}); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private async Task DoCallBack() |
|
||||||
{ |
|
||||||
List<HandleCallbackParam> list = new List<HandleCallbackParam>(); |
|
||||||
|
|
||||||
if(!taskQueue.TryDequeue(out var item)) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
list.Add(item); |
|
||||||
|
|
||||||
ReturnT result; |
|
||||||
try |
|
||||||
{ |
|
||||||
result = await _adminClient.Callback(list).ConfigureAwait(false); |
|
||||||
} |
|
||||||
catch (Exception ex){ |
|
||||||
_logger.LogError(ex,"trigger callback error:{error}",ex.Message); |
|
||||||
result = ReturnT.Failed(ex.Message); |
|
||||||
_retryQueue.Push(list); |
|
||||||
} |
|
||||||
|
|
||||||
LogCallBackResult(result, list); |
|
||||||
} |
|
||||||
|
|
||||||
private void LogCallBackResult(ReturnT result,List<HandleCallbackParam> list) |
|
||||||
{ |
|
||||||
foreach (var param in list) |
|
||||||
{ |
|
||||||
_jobLogger.LogSpecialFile(param.LogDateTime, param.LogId, result.Msg??"Success"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,174 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Concurrent; |
|
||||||
using System.Net.NetworkInformation; |
|
||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public class JobTaskQueue : IDisposable |
|
||||||
{ |
|
||||||
private readonly IJobLogger _jobLogger; |
|
||||||
private readonly ILogger<JobTaskQueue> _logger; |
|
||||||
private readonly ConcurrentQueue<TriggerParam> TASK_QUEUE = new ConcurrentQueue<TriggerParam>(); |
|
||||||
private readonly ConcurrentDictionary<long, byte> ID_IN_QUEUE = new ConcurrentDictionary<long, byte>(); |
|
||||||
private CancellationTokenSource _cancellationTokenSource; |
|
||||||
private Task _runTask; |
|
||||||
public JobTaskQueue(ITaskExecutor executor, IJobLogger jobLogger, ILogger<JobTaskQueue> logger) |
|
||||||
{ |
|
||||||
this.Executor = executor; |
|
||||||
this._jobLogger = jobLogger; |
|
||||||
this._logger = logger; |
|
||||||
} |
|
||||||
|
|
||||||
public ITaskExecutor Executor { get; } |
|
||||||
|
|
||||||
|
|
||||||
public event EventHandler<HandleCallbackParam> CallBack; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public bool IsRunning() |
|
||||||
{ |
|
||||||
return _cancellationTokenSource != null; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 覆盖之前的队列 |
|
||||||
/// </summary> |
|
||||||
/// <param name="triggerParam"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public ReturnT Replace(TriggerParam triggerParam) |
|
||||||
{ |
|
||||||
while (!TASK_QUEUE.IsEmpty) |
|
||||||
{ |
|
||||||
TASK_QUEUE.TryDequeue(out _); |
|
||||||
} |
|
||||||
Stop(); |
|
||||||
ID_IN_QUEUE.Clear(); |
|
||||||
|
|
||||||
return Push(triggerParam); |
|
||||||
} |
|
||||||
|
|
||||||
public ReturnT Push(TriggerParam triggerParam) |
|
||||||
{ |
|
||||||
if (!ID_IN_QUEUE.TryAdd(triggerParam.LogId, 0)) |
|
||||||
{ |
|
||||||
_logger.LogWarning("repeat job task,logId={logId},jobId={jobId}", triggerParam.LogId, triggerParam.JobId); |
|
||||||
return ReturnT.Failed("repeat job task!"); |
|
||||||
} |
|
||||||
|
|
||||||
//this._logger.LogWarning("add job with logId={logId},jobId={jobId}",triggerParam.LogId,triggerParam.JobId); |
|
||||||
|
|
||||||
this.TASK_QUEUE.Enqueue(triggerParam); |
|
||||||
StartTask(); |
|
||||||
return ReturnT.SUCCESS; |
|
||||||
} |
|
||||||
|
|
||||||
public void Stop() |
|
||||||
{ |
|
||||||
_cancellationTokenSource?.Cancel(); |
|
||||||
_cancellationTokenSource?.Dispose(); |
|
||||||
_cancellationTokenSource = null; |
|
||||||
|
|
||||||
//wait for task completed |
|
||||||
_runTask?.GetAwaiter().GetResult(); |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ |
|
||||||
while (!TASK_QUEUE.IsEmpty) |
|
||||||
{ |
|
||||||
TASK_QUEUE.TryDequeue(out _); |
|
||||||
} |
|
||||||
ID_IN_QUEUE.Clear(); |
|
||||||
Stop(); |
|
||||||
} |
|
||||||
|
|
||||||
private void StartTask() |
|
||||||
{ |
|
||||||
if (_cancellationTokenSource != null) |
|
||||||
{ |
|
||||||
return; //running |
|
||||||
} |
|
||||||
_cancellationTokenSource = new CancellationTokenSource(); |
|
||||||
var ct = _cancellationTokenSource.Token; |
|
||||||
|
|
||||||
_runTask = Task.Factory.StartNew(async () => |
|
||||||
{ |
|
||||||
|
|
||||||
//ct.ThrowIfCancellationRequested(); |
|
||||||
|
|
||||||
while (!ct.IsCancellationRequested) |
|
||||||
{ |
|
||||||
if (TASK_QUEUE.IsEmpty) |
|
||||||
{ |
|
||||||
//_logger.LogInformation("task queue is empty!"); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
ReturnT result = null; |
|
||||||
TriggerParam triggerParam = null; |
|
||||||
try |
|
||||||
{ |
|
||||||
|
|
||||||
if (TASK_QUEUE.TryDequeue(out triggerParam)) |
|
||||||
{ |
|
||||||
if (!ID_IN_QUEUE.TryRemove(triggerParam.LogId, out _)) |
|
||||||
{ |
|
||||||
_logger.LogWarning("remove queue failed,logId={logId},jobId={jobId},exists={exists}" |
|
||||||
, triggerParam.LogId, triggerParam.JobId, ID_IN_QUEUE.ContainsKey(triggerParam.LogId)); |
|
||||||
} |
|
||||||
//set log file; |
|
||||||
_jobLogger.SetLogFile(triggerParam.LogDateTime, triggerParam.LogId); |
|
||||||
|
|
||||||
_jobLogger.Log("<br>----------- xxl-job job execute start -----------<br>----------- Param:{0}", triggerParam.ExecutorParams); |
|
||||||
|
|
||||||
var exectorToken = ct; |
|
||||||
CancellationTokenSource timeoutCts = null; |
|
||||||
if (triggerParam.ExecutorTimeout > 0) |
|
||||||
{ |
|
||||||
timeoutCts = new CancellationTokenSource(triggerParam.ExecutorTimeout * 1000); |
|
||||||
exectorToken = CancellationTokenSource.CreateLinkedTokenSource(exectorToken, timeoutCts.Token).Token; |
|
||||||
} |
|
||||||
result = await Executor.Execute(triggerParam, exectorToken); |
|
||||||
if(timeoutCts != null && timeoutCts.IsCancellationRequested) |
|
||||||
{ |
|
||||||
result = ReturnT.FAIL_TIMEOUT; |
|
||||||
timeoutCts.Dispose(); |
|
||||||
timeoutCts = null; |
|
||||||
} |
|
||||||
|
|
||||||
_jobLogger.Log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- ReturnT:" + result.Code); |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
_logger.LogWarning("Dequeue Task Failed"); |
|
||||||
} |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
result = ReturnT.Failed("Dequeue Task Failed:" + ex.Message); |
|
||||||
_jobLogger.Log("<br>----------- JobThread Exception:" + ex.Message + "<br>----------- xxl-job job execute end(error) -----------"); |
|
||||||
} |
|
||||||
|
|
||||||
if (triggerParam != null) |
|
||||||
{ |
|
||||||
CallBack?.Invoke(this, new HandleCallbackParam(triggerParam, result ?? ReturnT.FAIL)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose(); |
|
||||||
_cancellationTokenSource = null; |
|
||||||
}, _cancellationTokenSource.Token); |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,136 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.IO; |
|
||||||
using System.Text; |
|
||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Json; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Queue |
|
||||||
{ |
|
||||||
public class RetryCallbackTaskQueue:IDisposable |
|
||||||
{ |
|
||||||
|
|
||||||
private readonly Action<HandleCallbackParam> _actionDoCallback; |
|
||||||
private readonly ILogger<RetryCallbackTaskQueue> _logger; |
|
||||||
|
|
||||||
private CancellationTokenSource _cancellation; |
|
||||||
private Task _runTask; |
|
||||||
private readonly string _backupFile; |
|
||||||
public RetryCallbackTaskQueue(string backupPath,Action<HandleCallbackParam> actionDoCallback,ILogger<RetryCallbackTaskQueue> logger) |
|
||||||
{ |
|
||||||
|
|
||||||
_actionDoCallback = actionDoCallback; |
|
||||||
_logger = logger; |
|
||||||
_backupFile = Path.Combine(backupPath, Constants.XxlJobRetryLogsFile); |
|
||||||
var dir = Path.GetDirectoryName(backupPath); |
|
||||||
if (!Directory.Exists(dir)) |
|
||||||
{ |
|
||||||
Directory.CreateDirectory(dir ?? throw new Exception("logs path is empty")); |
|
||||||
} |
|
||||||
|
|
||||||
StartQueue(); |
|
||||||
} |
|
||||||
|
|
||||||
private void StartQueue() |
|
||||||
{ |
|
||||||
_cancellation = new CancellationTokenSource(); |
|
||||||
var stopToken = this._cancellation.Token; |
|
||||||
_runTask = Task.Factory.StartNew(async () => |
|
||||||
{ |
|
||||||
while (!stopToken.IsCancellationRequested) |
|
||||||
{ |
|
||||||
await LoadFromFile(); |
|
||||||
await Task.Delay(Constants.CallbackRetryInterval,stopToken); |
|
||||||
} |
|
||||||
|
|
||||||
}, TaskCreationOptions.LongRunning); |
|
||||||
} |
|
||||||
|
|
||||||
private async Task LoadFromFile() |
|
||||||
{ |
|
||||||
var list = new List<HandleCallbackParam>(); |
|
||||||
|
|
||||||
if (!File.Exists(_backupFile)) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
using (StreamReader reader = new StreamReader(this._backupFile)) |
|
||||||
{ |
|
||||||
string nextLine; |
|
||||||
while ((nextLine = await reader.ReadLineAsync()) != null) |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
list.Add(Utf8Json.JsonSerializer.Deserialize<HandleCallbackParam>(nextLine, ProjectDefaultResolver.Instance)); |
|
||||||
} |
|
||||||
catch(Exception ex) |
|
||||||
{ |
|
||||||
_logger.LogError(ex,"read backup file error:{error}",ex.Message); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
try |
|
||||||
{ |
|
||||||
File.Delete(_backupFile); //ɾ³ý±¸·ÝÎļþ |
|
||||||
} |
|
||||||
|
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
_logger.LogError(ex, "delete backup file error:{error}", ex.Message); |
|
||||||
} |
|
||||||
if (list.Count > 0) |
|
||||||
{ |
|
||||||
foreach (var item in list) |
|
||||||
{ |
|
||||||
_actionDoCallback(item); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
public void Push(List<HandleCallbackParam> list) |
|
||||||
{ |
|
||||||
if (list?.Count == 0) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
try |
|
||||||
{ |
|
||||||
|
|
||||||
using (var writer = new StreamWriter(this._backupFile, true, Encoding.UTF8)) |
|
||||||
{ |
|
||||||
foreach (var item in list) |
|
||||||
{ |
|
||||||
if (item.CallbackRetryTimes >= Constants.MaxCallbackRetryTimes) |
|
||||||
{ |
|
||||||
this._logger.LogInformation("callback too many times and will be abandon,logId {logId}", item.LogId); |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
item.CallbackRetryTimes++; |
|
||||||
byte[] buffer = Utf8Json.JsonSerializer.Serialize(item,ProjectDefaultResolver.Instance); |
|
||||||
writer.WriteLine(Encoding.UTF8.GetString(buffer)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
this._logger.LogError(ex, "SaveCallbackParams error."); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ |
|
||||||
this._cancellation.Cancel(); |
|
||||||
this._runTask?.GetAwaiter().GetResult(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,40 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using Microsoft.Extensions.DependencyInjection; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// 负责响应RPC请求,调度任务执行器的工厂类 |
|
||||||
/// </summary> |
|
||||||
public class TaskExecutorFactory |
|
||||||
{ |
|
||||||
private readonly IServiceProvider _provider; |
|
||||||
|
|
||||||
private readonly Dictionary<string, ITaskExecutor> _cache = new Dictionary<string, ITaskExecutor>(); |
|
||||||
public TaskExecutorFactory(IServiceProvider provider) |
|
||||||
{ |
|
||||||
this._provider = provider; |
|
||||||
Initialize(); |
|
||||||
} |
|
||||||
|
|
||||||
private void Initialize() |
|
||||||
{ |
|
||||||
var executors = this._provider.GetServices<ITaskExecutor>(); |
|
||||||
|
|
||||||
var taskExecutors = executors as ITaskExecutor[] ?? executors.ToArray(); |
|
||||||
if (executors == null || !taskExecutors.Any()) return; |
|
||||||
|
|
||||||
foreach (var item in taskExecutors) |
|
||||||
{ |
|
||||||
this._cache.Add(item.GlueType,item); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public ITaskExecutor GetTaskExecutor(string glueType) |
|
||||||
{ |
|
||||||
return this._cache.TryGetValue(glueType, out var executor) ? executor : null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,35 +0,0 @@ |
|||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.TaskExecutors |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// 实现 IJobHandler的执行器 |
|
||||||
/// </summary> |
|
||||||
public class BeanTaskExecutor : ITaskExecutor |
|
||||||
{ |
|
||||||
private readonly IJobHandlerFactory _handlerFactory; |
|
||||||
private readonly IJobLogger _jobLogger; |
|
||||||
|
|
||||||
public BeanTaskExecutor(IJobHandlerFactory handlerFactory, IJobLogger jobLogger) |
|
||||||
{ |
|
||||||
this._handlerFactory = handlerFactory; |
|
||||||
this._jobLogger = jobLogger; |
|
||||||
} |
|
||||||
|
|
||||||
public string GlueType { get; } = Constants.GlueType.BEAN; |
|
||||||
|
|
||||||
public Task<ReturnT> Execute(TriggerParam triggerParam, CancellationToken cancellationToken) |
|
||||||
{ |
|
||||||
var handler = _handlerFactory.GetJobHandler(triggerParam.ExecutorHandler); |
|
||||||
|
|
||||||
if (handler == null) |
|
||||||
{ |
|
||||||
return Task.FromResult(ReturnT.Failed($"job handler [{triggerParam.ExecutorHandler} not found.")); |
|
||||||
} |
|
||||||
var context = new JobExecuteContext(this._jobLogger, triggerParam.ExecutorParams, cancellationToken); |
|
||||||
return handler.Execute(context); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using DotXxlJob.Core.Model; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core |
|
||||||
{ |
|
||||||
public interface ITaskExecutor |
|
||||||
{ |
|
||||||
string GlueType { get; } |
|
||||||
|
|
||||||
Task<ReturnT> Execute(TriggerParam triggerParam, CancellationToken cancellationToken); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
using System; |
|
||||||
using Xunit; |
|
||||||
|
|
||||||
namespace DotXxlJob.Core.Tests |
|
||||||
{ |
|
||||||
public class UnitTest1 |
|
||||||
{ |
|
||||||
[Fact] |
|
||||||
public void Test1() |
|
||||||
{ |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue